feat: implement File System API support (#41419)
This commit is contained in:
		
					parent
					
						
							
								41ba963392
							
						
					
				
			
			
				commit
				
					
						344aba0838
					
				
			
		
					 23 changed files with 1562 additions and 9 deletions
				
			
		| 
						 | 
				
			
			@ -33,6 +33,8 @@ static_library("chrome") {
 | 
			
		|||
    "//chrome/browser/devtools/visual_logging.h",
 | 
			
		||||
    "//chrome/browser/extensions/global_shortcut_listener.cc",
 | 
			
		||||
    "//chrome/browser/extensions/global_shortcut_listener.h",
 | 
			
		||||
    "//chrome/browser/file_system_access/file_system_access_features.cc",
 | 
			
		||||
    "//chrome/browser/file_system_access/file_system_access_features.h",
 | 
			
		||||
    "//chrome/browser/icon_loader.cc",
 | 
			
		||||
    "//chrome/browser/icon_loader.h",
 | 
			
		||||
    "//chrome/browser/icon_manager.cc",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -818,15 +818,10 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
 | 
			
		|||
    * `top-level-storage-access` -  Allow top-level sites to request third-party cookie access on behalf of embedded content originating from another site in the same related website set using the [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API).
 | 
			
		||||
    * `window-management` - Request access to enumerate screens using the [`getScreenDetails`](https://developer.chrome.com/en/articles/multi-screen-window-placement/) API.
 | 
			
		||||
    * `unknown` - An unrecognized permission request.
 | 
			
		||||
    * `fileSystem` - Request access to read, write, and file management capabilities using the [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API).
 | 
			
		||||
  * `callback` Function
 | 
			
		||||
    * `permissionGranted` boolean - Allow or deny the permission.
 | 
			
		||||
  * `details` Object - Some properties are only available on certain permission types.
 | 
			
		||||
    * `externalURL` string (optional) - The url of the `openExternal` request.
 | 
			
		||||
    * `securityOrigin` string (optional) - The security origin of the `media` request.
 | 
			
		||||
    * `mediaTypes` string[] (optional) - The types of media access being requested, elements can be `video`
 | 
			
		||||
      or `audio`
 | 
			
		||||
    * `requestingUrl` string - The last URL the requesting frame loaded
 | 
			
		||||
    * `isMainFrame` boolean - Whether the frame making the request is the main frame
 | 
			
		||||
  * `details` [PermissionRequest](structures/permission-request.md)  | [FilesystemPermissionRequest](structures/filesystem-permission-request.md) | [MediaAccessPermissionRequest](structures/media-access-permission-request.md) | [OpenExternalPermissionRequest](structures/open-external-permission-request.md) - Additional information about the permission being requested.
 | 
			
		||||
 | 
			
		||||
Sets the handler which can be used to respond to permission requests for the `session`.
 | 
			
		||||
Calling `callback(true)` will allow the permission and `callback(false)` will reject it.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								docs/api/structures/filesystem-permission-request.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/api/structures/filesystem-permission-request.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
# FilesystemPermissionRequest Object extends `PermissionRequest`
 | 
			
		||||
 | 
			
		||||
* `filePath` string (optional) - The path of the `fileSystem` request.
 | 
			
		||||
* `isDirectory` boolean (optional) - Whether the `fileSystem` request is a directory.
 | 
			
		||||
* `fileAccessType` string (optional) - The access type of the `fileSystem` request. Can be `writable` or `readable`.
 | 
			
		||||
							
								
								
									
										5
									
								
								docs/api/structures/media-access-permission-request.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/api/structures/media-access-permission-request.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
# MediaAccessPermissionRequest Object extends `PermissionRequest`
 | 
			
		||||
 | 
			
		||||
* `securityOrigin` string (optional) - The security origin of the request.
 | 
			
		||||
* `mediaTypes` string[] (optional) - The types of media access being requested - elements can be `video`
 | 
			
		||||
  or `audio`.
 | 
			
		||||
							
								
								
									
										3
									
								
								docs/api/structures/open-external-permission-request.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/api/structures/open-external-permission-request.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
# OpenExternalPermissionRequest Object extends `PermissionRequest`
 | 
			
		||||
 | 
			
		||||
* `externalURL` string (optional) - The url of the `openExternal` request.
 | 
			
		||||
							
								
								
									
										4
									
								
								docs/api/structures/permission-request.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docs/api/structures/permission-request.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
# PermissionRequest Object
 | 
			
		||||
 | 
			
		||||
* `requestingUrl` string - The last URL the requesting frame loaded.
 | 
			
		||||
* `isMainFrame` boolean - Whether the frame making the request is the main frame.
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +89,7 @@ auto_filenames = {
 | 
			
		|||
    "docs/api/structures/extension.md",
 | 
			
		||||
    "docs/api/structures/file-filter.md",
 | 
			
		||||
    "docs/api/structures/file-path-with-headers.md",
 | 
			
		||||
    "docs/api/structures/filesystem-permission-request.md",
 | 
			
		||||
    "docs/api/structures/gpu-feature-status.md",
 | 
			
		||||
    "docs/api/structures/hid-device.md",
 | 
			
		||||
    "docs/api/structures/input-event.md",
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +100,7 @@ auto_filenames = {
 | 
			
		|||
    "docs/api/structures/jump-list-item.md",
 | 
			
		||||
    "docs/api/structures/keyboard-event.md",
 | 
			
		||||
    "docs/api/structures/keyboard-input-event.md",
 | 
			
		||||
    "docs/api/structures/media-access-permission-request.md",
 | 
			
		||||
    "docs/api/structures/memory-info.md",
 | 
			
		||||
    "docs/api/structures/memory-usage-details.md",
 | 
			
		||||
    "docs/api/structures/mime-typed-buffer.md",
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +108,9 @@ auto_filenames = {
 | 
			
		|||
    "docs/api/structures/mouse-wheel-input-event.md",
 | 
			
		||||
    "docs/api/structures/notification-action.md",
 | 
			
		||||
    "docs/api/structures/notification-response.md",
 | 
			
		||||
    "docs/api/structures/open-external-permission-request.md",
 | 
			
		||||
    "docs/api/structures/payment-discount.md",
 | 
			
		||||
    "docs/api/structures/permission-request.md",
 | 
			
		||||
    "docs/api/structures/point.md",
 | 
			
		||||
    "docs/api/structures/post-body.md",
 | 
			
		||||
    "docs/api/structures/printer-info.md",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -380,6 +380,10 @@ filenames = {
 | 
			
		|||
    "shell/browser/file_select_helper.cc",
 | 
			
		||||
    "shell/browser/file_select_helper.h",
 | 
			
		||||
    "shell/browser/file_select_helper_mac.mm",
 | 
			
		||||
    "shell/browser/file_system_access/file_system_access_permission_context.cc",
 | 
			
		||||
    "shell/browser/file_system_access/file_system_access_permission_context.h",
 | 
			
		||||
    "shell/browser/file_system_access/file_system_access_permission_context_factory.cc",
 | 
			
		||||
    "shell/browser/file_system_access/file_system_access_permission_context_factory.h",
 | 
			
		||||
    "shell/browser/font_defaults.cc",
 | 
			
		||||
    "shell/browser/font_defaults.h",
 | 
			
		||||
    "shell/browser/hid/electron_hid_delegate.cc",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -129,3 +129,4 @@ build_run_reclient_cfg_generator_after_chrome.patch
 | 
			
		|||
fix_suppress_clang_-wimplicit-const-int-float-conversion_in.patch
 | 
			
		||||
fix_getcursorscreenpoint_wrongly_returns_0_0.patch
 | 
			
		||||
fix_add_support_for_skipping_first_2_no-op_refreshes_in_thumb_cap.patch
 | 
			
		||||
refactor_expose_file_system_access_blocklist.patch
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,303 @@
 | 
			
		|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
			
		||||
From: Shelley Vohr <shelley.vohr@gmail.com>
 | 
			
		||||
Date: Wed, 27 Mar 2024 10:47:48 +0100
 | 
			
		||||
Subject: refactor: expose file system access blocklist
 | 
			
		||||
 | 
			
		||||
This CL exposes the file system access blocklist publicly so that we can leverage
 | 
			
		||||
it in Electron and prevent drift from Chrome's blocklist. We should look for a way
 | 
			
		||||
to upstream this change to Chrome.
 | 
			
		||||
 | 
			
		||||
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
 | 
			
		||||
index 9c644d678d6d811ae5679594c0574fc0d8607f62..792cd62da17239ca6933930880af23754e4ab3d3 100644
 | 
			
		||||
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
 | 
			
		||||
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
 | 
			
		||||
@@ -38,7 +38,6 @@
 | 
			
		||||
 #include "chrome/browser/profiles/profile_manager.h"
 | 
			
		||||
 #include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
 | 
			
		||||
 #include "chrome/browser/ui/file_system_access_dialogs.h"
 | 
			
		||||
-#include "chrome/common/chrome_paths.h"
 | 
			
		||||
 #include "chrome/common/pdf_util.h"
 | 
			
		||||
 #include "chrome/grit/generated_resources.h"
 | 
			
		||||
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 | 
			
		||||
@@ -222,121 +221,6 @@ bool MaybeIsLocalUNCPath(const base::FilePath& path) {
 | 
			
		||||
 }
 | 
			
		||||
 #endif
 | 
			
		||||
 
 | 
			
		||||
-// Sentinel used to indicate that no PathService key is specified for a path in
 | 
			
		||||
-// the struct below.
 | 
			
		||||
-constexpr const int kNoBasePathKey = -1;
 | 
			
		||||
-
 | 
			
		||||
-enum BlockType {
 | 
			
		||||
-  kBlockAllChildren,
 | 
			
		||||
-  kBlockNestedDirectories,
 | 
			
		||||
-  kDontBlockChildren
 | 
			
		||||
-};
 | 
			
		||||
-
 | 
			
		||||
-const struct {
 | 
			
		||||
-  // base::BasePathKey value (or one of the platform specific extensions to it)
 | 
			
		||||
-  // for a path that should be blocked. Specify kNoBasePathKey if |path| should
 | 
			
		||||
-  // be used instead.
 | 
			
		||||
-  int base_path_key;
 | 
			
		||||
-
 | 
			
		||||
-  // Explicit path to block instead of using |base_path_key|. Set to nullptr to
 | 
			
		||||
-  // use |base_path_key| on its own. If both |base_path_key| and |path| are set,
 | 
			
		||||
-  // |path| is treated relative to the path |base_path_key| resolves to.
 | 
			
		||||
-  const base::FilePath::CharType* path;
 | 
			
		||||
-
 | 
			
		||||
-  // If this is set to kDontBlockChildren, only the given path and its parents
 | 
			
		||||
-  // are blocked. If this is set to kBlockAllChildren, all children of the given
 | 
			
		||||
-  // path are blocked as well. Finally if this is set to kBlockNestedDirectories
 | 
			
		||||
-  // access is allowed to individual files in the directory, but nested
 | 
			
		||||
-  // directories are still blocked.
 | 
			
		||||
-  // The BlockType of the nearest ancestor of a path to check is what ultimately
 | 
			
		||||
-  // determines if a path is blocked or not. If a blocked path is a descendent
 | 
			
		||||
-  // of another blocked path, then it may override the child-blocking policy of
 | 
			
		||||
-  // its ancestor. For example, if /home blocks all children, but
 | 
			
		||||
-  // /home/downloads does not, then /home/downloads/file.ext will *not* be
 | 
			
		||||
-  // blocked.
 | 
			
		||||
-  BlockType type;
 | 
			
		||||
-} kBlockedPaths[] = {
 | 
			
		||||
-    // Don't allow users to share their entire home directory, entire desktop or
 | 
			
		||||
-    // entire documents folder, but do allow sharing anything inside those
 | 
			
		||||
-    // directories not otherwise blocked.
 | 
			
		||||
-    {base::DIR_HOME, nullptr, kDontBlockChildren},
 | 
			
		||||
-    {base::DIR_USER_DESKTOP, nullptr, kDontBlockChildren},
 | 
			
		||||
-    {chrome::DIR_USER_DOCUMENTS, nullptr, kDontBlockChildren},
 | 
			
		||||
-    // Similar restrictions for the downloads directory.
 | 
			
		||||
-    {chrome::DIR_DEFAULT_DOWNLOADS, nullptr, kDontBlockChildren},
 | 
			
		||||
-    {chrome::DIR_DEFAULT_DOWNLOADS_SAFE, nullptr, kDontBlockChildren},
 | 
			
		||||
-    // The Chrome installation itself should not be modified by the web.
 | 
			
		||||
-    {base::DIR_EXE, nullptr, kBlockAllChildren},
 | 
			
		||||
-#if !BUILDFLAG(IS_FUCHSIA)
 | 
			
		||||
-    {base::DIR_MODULE, nullptr, kBlockAllChildren},
 | 
			
		||||
-#endif
 | 
			
		||||
-    {base::DIR_ASSETS, nullptr, kBlockAllChildren},
 | 
			
		||||
-    // And neither should the configuration of at least the currently running
 | 
			
		||||
-    // Chrome instance (note that this does not take --user-data-dir command
 | 
			
		||||
-    // line overrides into account).
 | 
			
		||||
-    {chrome::DIR_USER_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
-    // ~/.ssh is pretty sensitive on all platforms, so block access to that.
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL(".ssh"), kBlockAllChildren},
 | 
			
		||||
-    // And limit access to ~/.gnupg as well.
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL(".gnupg"), kBlockAllChildren},
 | 
			
		||||
-#if BUILDFLAG(IS_WIN)
 | 
			
		||||
-    // Some Windows specific directories to block, basically all apps, the
 | 
			
		||||
-    // operating system itself, as well as configuration data for apps.
 | 
			
		||||
-    {base::DIR_PROGRAM_FILES, nullptr, kBlockAllChildren},
 | 
			
		||||
-    {base::DIR_PROGRAM_FILESX86, nullptr, kBlockAllChildren},
 | 
			
		||||
-    {base::DIR_PROGRAM_FILES6432, nullptr, kBlockAllChildren},
 | 
			
		||||
-    {base::DIR_WINDOWS, nullptr, kBlockAllChildren},
 | 
			
		||||
-    {base::DIR_ROAMING_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
-    {base::DIR_LOCAL_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
-    {base::DIR_COMMON_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
-    // Opening a file from an MTP device, such as a smartphone or a camera, is
 | 
			
		||||
-    // implemented by Windows as opening a file in the temporary internet files
 | 
			
		||||
-    // directory. To support that, allow opening files in that directory, but
 | 
			
		||||
-    // not whole directories.
 | 
			
		||||
-    {base::DIR_IE_INTERNET_CACHE, nullptr, kBlockNestedDirectories},
 | 
			
		||||
-#endif
 | 
			
		||||
-#if BUILDFLAG(IS_MAC)
 | 
			
		||||
-    // Similar Mac specific blocks.
 | 
			
		||||
-    {base::DIR_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL("Library"), kBlockAllChildren},
 | 
			
		||||
-    // Allow access to other cloud files, such as Google Drive.
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL("Library/CloudStorage"),
 | 
			
		||||
-     kDontBlockChildren},
 | 
			
		||||
-    // Allow the site to interact with data from its corresponding natively
 | 
			
		||||
-    // installed (sandboxed) application. It would be nice to limit a site to
 | 
			
		||||
-    // access only _its_ corresponding natively installed application,
 | 
			
		||||
-    // but unfortunately there's no straightforward way to do that. See
 | 
			
		||||
-    // https://crbug.com/984641#c22.
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL("Library/Containers"),
 | 
			
		||||
-     kDontBlockChildren},
 | 
			
		||||
-    // Allow access to iCloud files...
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL("Library/Mobile Documents"),
 | 
			
		||||
-     kDontBlockChildren},
 | 
			
		||||
-    // ... which may also appear at this directory.
 | 
			
		||||
-    {base::DIR_HOME,
 | 
			
		||||
-     FILE_PATH_LITERAL("Library/Mobile Documents/com~apple~CloudDocs"),
 | 
			
		||||
-     kDontBlockChildren},
 | 
			
		||||
-#endif
 | 
			
		||||
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 | 
			
		||||
-    // On Linux also block access to devices via /dev.
 | 
			
		||||
-    {kNoBasePathKey, FILE_PATH_LITERAL("/dev"), kBlockAllChildren},
 | 
			
		||||
-    // And security sensitive data in /proc and /sys.
 | 
			
		||||
-    {kNoBasePathKey, FILE_PATH_LITERAL("/proc"), kBlockAllChildren},
 | 
			
		||||
-    {kNoBasePathKey, FILE_PATH_LITERAL("/sys"), kBlockAllChildren},
 | 
			
		||||
-    // And system files in /boot and /etc.
 | 
			
		||||
-    {kNoBasePathKey, FILE_PATH_LITERAL("/boot"), kBlockAllChildren},
 | 
			
		||||
-    {kNoBasePathKey, FILE_PATH_LITERAL("/etc"), kBlockAllChildren},
 | 
			
		||||
-    // And block all of ~/.config, matching the similar restrictions on mac
 | 
			
		||||
-    // and windows.
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL(".config"), kBlockAllChildren},
 | 
			
		||||
-    // Block ~/.dbus as well, just in case, although there probably isn't much a
 | 
			
		||||
-    // website can do with access to that directory and its contents.
 | 
			
		||||
-    {base::DIR_HOME, FILE_PATH_LITERAL(".dbus"), kBlockAllChildren},
 | 
			
		||||
-#endif
 | 
			
		||||
-    // TODO(https://crbug.com/984641): Refine this list, for example add
 | 
			
		||||
-    // XDG_CONFIG_HOME when it is not set ~/.config?
 | 
			
		||||
-};
 | 
			
		||||
-
 | 
			
		||||
 // Describes a rule for blocking a directory, which can be constructed
 | 
			
		||||
 // dynamically (based on state) or statically (from kBlockedPaths).
 | 
			
		||||
 struct BlockPathRule {
 | 
			
		||||
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h
 | 
			
		||||
index 8bc8257b603a88e56f77dcf7d72aa9dad45880db..484f98c68b0dc860a6482e923df2379133c57749 100644
 | 
			
		||||
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h
 | 
			
		||||
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.h
 | 
			
		||||
@@ -17,12 +17,13 @@
 | 
			
		||||
 #include "base/time/default_clock.h"
 | 
			
		||||
 #include "chrome/browser/file_system_access/file_system_access_features.h"
 | 
			
		||||
 #include "chrome/browser/file_system_access/file_system_access_permission_request_manager.h"
 | 
			
		||||
-#include "components/enterprise/buildflags/buildflags.h"
 | 
			
		||||
+#include "chrome/common/chrome_paths.h"
 | 
			
		||||
 #include "components/permissions/features.h"
 | 
			
		||||
 #include "components/permissions/object_permission_context_base.h"
 | 
			
		||||
 #include "content/public/browser/file_system_access_permission_context.h"
 | 
			
		||||
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom-forward.h"
 | 
			
		||||
 
 | 
			
		||||
+
 | 
			
		||||
 #if !BUILDFLAG(IS_ANDROID)
 | 
			
		||||
 #include "chrome/browser/permissions/one_time_permissions_tracker.h"
 | 
			
		||||
 #include "chrome/browser/permissions/one_time_permissions_tracker_observer.h"
 | 
			
		||||
@@ -30,7 +31,8 @@
 | 
			
		||||
 #include "chrome/browser/web_applications/web_app_install_manager_observer.h"
 | 
			
		||||
 #endif
 | 
			
		||||
 
 | 
			
		||||
-#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
 | 
			
		||||
+#if 0
 | 
			
		||||
+#include "components/enterprise/buildflags/buildflags.h"
 | 
			
		||||
 #include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h"
 | 
			
		||||
 #include "components/enterprise/common/files_scan_data.h"
 | 
			
		||||
 #endif
 | 
			
		||||
@@ -331,6 +333,121 @@ class ChromeFileSystemAccessPermissionContext
 | 
			
		||||
   // chrome://settings/content/filesystem UI.
 | 
			
		||||
   static constexpr char kPermissionPathKey[] = "path";
 | 
			
		||||
 
 | 
			
		||||
+  // Sentinel used to indicate that no PathService key is specified for a path in
 | 
			
		||||
+  // the struct below.
 | 
			
		||||
+  static constexpr int kNoBasePathKey = -1;
 | 
			
		||||
+
 | 
			
		||||
+  enum BlockType {
 | 
			
		||||
+    kBlockAllChildren,
 | 
			
		||||
+    kBlockNestedDirectories,
 | 
			
		||||
+    kDontBlockChildren
 | 
			
		||||
+  };
 | 
			
		||||
+
 | 
			
		||||
+  static constexpr struct {
 | 
			
		||||
+    // base::BasePathKey value (or one of the platform specific extensions to it)
 | 
			
		||||
+    // for a path that should be blocked. Specify kNoBasePathKey if |path| should
 | 
			
		||||
+    // be used instead.
 | 
			
		||||
+    int base_path_key;
 | 
			
		||||
+
 | 
			
		||||
+    // Explicit path to block instead of using |base_path_key|. Set to nullptr to
 | 
			
		||||
+    // use |base_path_key| on its own. If both |base_path_key| and |path| are set,
 | 
			
		||||
+    // |path| is treated relative to the path |base_path_key| resolves to.
 | 
			
		||||
+    const base::FilePath::CharType* path;
 | 
			
		||||
+
 | 
			
		||||
+    // If this is set to kDontBlockChildren, only the given path and its parents
 | 
			
		||||
+    // are blocked. If this is set to kBlockAllChildren, all children of the given
 | 
			
		||||
+    // path are blocked as well. Finally if this is set to kBlockNestedDirectories
 | 
			
		||||
+    // access is allowed to individual files in the directory, but nested
 | 
			
		||||
+    // directories are still blocked.
 | 
			
		||||
+    // The BlockType of the nearest ancestor of a path to check is what ultimately
 | 
			
		||||
+    // determines if a path is blocked or not. If a blocked path is a descendent
 | 
			
		||||
+    // of another blocked path, then it may override the child-blocking policy of
 | 
			
		||||
+    // its ancestor. For example, if /home blocks all children, but
 | 
			
		||||
+    // /home/downloads does not, then /home/downloads/file.ext will *not* be
 | 
			
		||||
+    // blocked.
 | 
			
		||||
+    BlockType type;
 | 
			
		||||
+  } kBlockedPaths[] = {
 | 
			
		||||
+      // Don't allow users to share their entire home directory, entire desktop or
 | 
			
		||||
+      // entire documents folder, but do allow sharing anything inside those
 | 
			
		||||
+      // directories not otherwise blocked.
 | 
			
		||||
+      {base::DIR_HOME, nullptr, kDontBlockChildren},
 | 
			
		||||
+      {base::DIR_USER_DESKTOP, nullptr, kDontBlockChildren},
 | 
			
		||||
+      {chrome::DIR_USER_DOCUMENTS, nullptr, kDontBlockChildren},
 | 
			
		||||
+      // Similar restrictions for the downloads directory.
 | 
			
		||||
+      {chrome::DIR_DEFAULT_DOWNLOADS, nullptr, kDontBlockChildren},
 | 
			
		||||
+      {chrome::DIR_DEFAULT_DOWNLOADS_SAFE, nullptr, kDontBlockChildren},
 | 
			
		||||
+      // The Chrome installation itself should not be modified by the web.
 | 
			
		||||
+      {base::DIR_EXE, nullptr, kBlockAllChildren},
 | 
			
		||||
+  #if !BUILDFLAG(IS_FUCHSIA)
 | 
			
		||||
+      {base::DIR_MODULE, nullptr, kBlockAllChildren},
 | 
			
		||||
+  #endif
 | 
			
		||||
+      {base::DIR_ASSETS, nullptr, kBlockAllChildren},
 | 
			
		||||
+      // And neither should the configuration of at least the currently running
 | 
			
		||||
+      // Chrome instance (note that this does not take --user-data-dir command
 | 
			
		||||
+      // line overrides into account).
 | 
			
		||||
+      {chrome::DIR_USER_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
+      // ~/.ssh is pretty sensitive on all platforms, so block access to that.
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL(".ssh"), kBlockAllChildren},
 | 
			
		||||
+      // And limit access to ~/.gnupg as well.
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL(".gnupg"), kBlockAllChildren},
 | 
			
		||||
+  #if BUILDFLAG(IS_WIN)
 | 
			
		||||
+      // Some Windows specific directories to block, basically all apps, the
 | 
			
		||||
+      // operating system itself, as well as configuration data for apps.
 | 
			
		||||
+      {base::DIR_PROGRAM_FILES, nullptr, kBlockAllChildren},
 | 
			
		||||
+      {base::DIR_PROGRAM_FILESX86, nullptr, kBlockAllChildren},
 | 
			
		||||
+      {base::DIR_PROGRAM_FILES6432, nullptr, kBlockAllChildren},
 | 
			
		||||
+      {base::DIR_WINDOWS, nullptr, kBlockAllChildren},
 | 
			
		||||
+      {base::DIR_ROAMING_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
+      {base::DIR_LOCAL_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
+      {base::DIR_COMMON_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
+      // Opening a file from an MTP device, such as a smartphone or a camera, is
 | 
			
		||||
+      // implemented by Windows as opening a file in the temporary internet files
 | 
			
		||||
+      // directory. To support that, allow opening files in that directory, but
 | 
			
		||||
+      // not whole directories.
 | 
			
		||||
+      {base::DIR_IE_INTERNET_CACHE, nullptr, kBlockNestedDirectories},
 | 
			
		||||
+  #endif
 | 
			
		||||
+  #if BUILDFLAG(IS_MAC)
 | 
			
		||||
+      // Similar Mac specific blocks.
 | 
			
		||||
+      {base::DIR_APP_DATA, nullptr, kBlockAllChildren},
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL("Library"), kBlockAllChildren},
 | 
			
		||||
+      // Allow access to other cloud files, such as Google Drive.
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL("Library/CloudStorage"),
 | 
			
		||||
+      kDontBlockChildren},
 | 
			
		||||
+      // Allow the site to interact with data from its corresponding natively
 | 
			
		||||
+      // installed (sandboxed) application. It would be nice to limit a site to
 | 
			
		||||
+      // access only _its_ corresponding natively installed application,
 | 
			
		||||
+      // but unfortunately there's no straightforward way to do that. See
 | 
			
		||||
+      // https://crbug.com/984641#c22.
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL("Library/Containers"),
 | 
			
		||||
+      kDontBlockChildren},
 | 
			
		||||
+      // Allow access to iCloud files...
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL("Library/Mobile Documents"),
 | 
			
		||||
+      kDontBlockChildren},
 | 
			
		||||
+      // ... which may also appear at this directory.
 | 
			
		||||
+      {base::DIR_HOME,
 | 
			
		||||
+      FILE_PATH_LITERAL("Library/Mobile Documents/com~apple~CloudDocs"),
 | 
			
		||||
+      kDontBlockChildren},
 | 
			
		||||
+  #endif
 | 
			
		||||
+  #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 | 
			
		||||
+      // On Linux also block access to devices via /dev.
 | 
			
		||||
+      {kNoBasePathKey, FILE_PATH_LITERAL("/dev"), kBlockAllChildren},
 | 
			
		||||
+      // And security sensitive data in /proc and /sys.
 | 
			
		||||
+      {kNoBasePathKey, FILE_PATH_LITERAL("/proc"), kBlockAllChildren},
 | 
			
		||||
+      {kNoBasePathKey, FILE_PATH_LITERAL("/sys"), kBlockAllChildren},
 | 
			
		||||
+      // And system files in /boot and /etc.
 | 
			
		||||
+      {kNoBasePathKey, FILE_PATH_LITERAL("/boot"), kBlockAllChildren},
 | 
			
		||||
+      {kNoBasePathKey, FILE_PATH_LITERAL("/etc"), kBlockAllChildren},
 | 
			
		||||
+      // And block all of ~/.config, matching the similar restrictions on mac
 | 
			
		||||
+      // and windows.
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL(".config"), kBlockAllChildren},
 | 
			
		||||
+      // Block ~/.dbus as well, just in case, although there probably isn't much a
 | 
			
		||||
+      // website can do with access to that directory and its contents.
 | 
			
		||||
+      {base::DIR_HOME, FILE_PATH_LITERAL(".dbus"), kBlockAllChildren},
 | 
			
		||||
+  #endif
 | 
			
		||||
+      // TODO(https://crbug.com/984641): Refine this list, for example add
 | 
			
		||||
+      // XDG_CONFIG_HOME when it is not set ~/.config?
 | 
			
		||||
+  };
 | 
			
		||||
+
 | 
			
		||||
  protected:
 | 
			
		||||
   SEQUENCE_CHECKER(sequence_checker_);
 | 
			
		||||
 
 | 
			
		||||
@@ -350,7 +467,7 @@ class ChromeFileSystemAccessPermissionContext
 | 
			
		||||
 
 | 
			
		||||
   void PermissionGrantDestroyed(PermissionGrantImpl* grant);
 | 
			
		||||
 
 | 
			
		||||
-#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
 | 
			
		||||
+#if 0
 | 
			
		||||
   void OnContentAnalysisComplete(
 | 
			
		||||
       std::vector<PathInfo> entries,
 | 
			
		||||
       EntriesAllowedByEnterprisePolicyCallback callback,
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +45,7 @@
 | 
			
		|||
#include "shell/browser/electron_browser_main_parts.h"
 | 
			
		||||
#include "shell/browser/electron_download_manager_delegate.h"
 | 
			
		||||
#include "shell/browser/electron_permission_manager.h"
 | 
			
		||||
#include "shell/browser/file_system_access/file_system_access_permission_context_factory.h"
 | 
			
		||||
#include "shell/browser/net/resolve_proxy_helper.h"
 | 
			
		||||
#include "shell/browser/protocol_registry.h"
 | 
			
		||||
#include "shell/browser/special_storage_policy.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -533,6 +534,11 @@ ElectronBrowserContext::GetReduceAcceptLanguageControllerDelegate() {
 | 
			
		|||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
content::FileSystemAccessPermissionContext*
 | 
			
		||||
ElectronBrowserContext::GetFileSystemAccessPermissionContext() {
 | 
			
		||||
  return FileSystemAccessPermissionContextFactory::GetForBrowserContext(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResolveProxyHelper* ElectronBrowserContext::GetResolveProxyHelper() {
 | 
			
		||||
  if (!resolve_proxy_helper_) {
 | 
			
		||||
    resolve_proxy_helper_ = base::MakeRefCounted<ResolveProxyHelper>(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -150,6 +150,8 @@ class ElectronBrowserContext : public content::BrowserContext {
 | 
			
		|||
  content::StorageNotificationService* GetStorageNotificationService() override;
 | 
			
		||||
  content::ReduceAcceptLanguageControllerDelegate*
 | 
			
		||||
  GetReduceAcceptLanguageControllerDelegate() override;
 | 
			
		||||
  content::FileSystemAccessPermissionContext*
 | 
			
		||||
  GetFileSystemAccessPermissionContext() override;
 | 
			
		||||
 | 
			
		||||
  CookieChangeNotifier* cookie_change_notifier() const {
 | 
			
		||||
    return cookie_change_notifier_.get();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,799 @@
 | 
			
		|||
// Copyright (c) 2024 Microsoft, GmbH
 | 
			
		||||
// Use of this source code is governed by the MIT license that can be
 | 
			
		||||
// found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "base/base_paths.h"
 | 
			
		||||
#include "base/files/file_path.h"
 | 
			
		||||
#include "base/json/values_util.h"
 | 
			
		||||
#include "base/path_service.h"
 | 
			
		||||
#include "base/task/thread_pool.h"
 | 
			
		||||
#include "base/values.h"
 | 
			
		||||
#include "chrome/browser/browser_process.h"
 | 
			
		||||
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"  // nogncheck
 | 
			
		||||
#include "chrome/browser/file_system_access/file_system_access_features.h"
 | 
			
		||||
#include "chrome/common/chrome_paths.h"
 | 
			
		||||
#include "chrome/grit/generated_resources.h"
 | 
			
		||||
#include "content/public/browser/browser_context.h"
 | 
			
		||||
#include "content/public/browser/browser_thread.h"
 | 
			
		||||
#include "content/public/browser/disallow_activation_reason.h"
 | 
			
		||||
#include "content/public/browser/render_frame_host.h"
 | 
			
		||||
#include "content/public/browser/render_process_host.h"
 | 
			
		||||
#include "content/public/browser/web_contents.h"
 | 
			
		||||
#include "shell/browser/electron_permission_manager.h"
 | 
			
		||||
#include "shell/browser/web_contents_permission_helper.h"
 | 
			
		||||
#include "shell/common/gin_converters/file_path_converter.h"
 | 
			
		||||
#include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h"
 | 
			
		||||
#include "ui/base/l10n/l10n_util.h"
 | 
			
		||||
#include "url/origin.h"
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
using BlockType = ChromeFileSystemAccessPermissionContext::BlockType;
 | 
			
		||||
using HandleType = content::FileSystemAccessPermissionContext::HandleType;
 | 
			
		||||
using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
 | 
			
		||||
using blink::mojom::PermissionStatus;
 | 
			
		||||
 | 
			
		||||
#if BUILDFLAG(IS_WIN)
 | 
			
		||||
[[nodiscard]] constexpr bool ContainsInvalidDNSCharacter(
 | 
			
		||||
    base::FilePath::StringType hostname) {
 | 
			
		||||
  return !base::ranges::all_of(hostname, [](base::FilePath::CharType c) {
 | 
			
		||||
    return (c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z') ||
 | 
			
		||||
           (c >= L'0' && c <= L'9') || (c == L'.') || (c == L'-');
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MaybeIsLocalUNCPath(const base::FilePath& path) {
 | 
			
		||||
  if (!path.IsNetwork()) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const std::vector<base::FilePath::StringType> components =
 | 
			
		||||
      path.GetComponents();
 | 
			
		||||
 | 
			
		||||
  // Check for server name that could represent a local system. We only
 | 
			
		||||
  // check for a very short list, as it is impossible to cover all different
 | 
			
		||||
  // variants on Windows.
 | 
			
		||||
  if (components.size() >= 2 &&
 | 
			
		||||
      (base::FilePath::CompareEqualIgnoreCase(components[1],
 | 
			
		||||
                                              FILE_PATH_LITERAL("localhost")) ||
 | 
			
		||||
       components[1] == FILE_PATH_LITERAL("127.0.0.1") ||
 | 
			
		||||
       components[1] == FILE_PATH_LITERAL(".") ||
 | 
			
		||||
       components[1] == FILE_PATH_LITERAL("?") ||
 | 
			
		||||
       ContainsInvalidDNSCharacter(components[1]))) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // In case we missed the server name check above, we also check for shares
 | 
			
		||||
  // ending with '$' as they represent pre-defined shares, including the local
 | 
			
		||||
  // drives.
 | 
			
		||||
  for (size_t i = 2; i < components.size(); ++i) {
 | 
			
		||||
    if (components[i].back() == L'$') {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Describes a rule for blocking a directory, which can be constructed
 | 
			
		||||
// dynamically (based on state) or statically (from kBlockedPaths).
 | 
			
		||||
struct BlockPathRule {
 | 
			
		||||
  base::FilePath path;
 | 
			
		||||
  BlockType type;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool ShouldBlockAccessToPath(const base::FilePath& path,
 | 
			
		||||
                             HandleType handle_type,
 | 
			
		||||
                             std::vector<BlockPathRule> rules) {
 | 
			
		||||
  DCHECK(!path.empty());
 | 
			
		||||
  DCHECK(path.IsAbsolute());
 | 
			
		||||
 | 
			
		||||
#if BUILDFLAG(IS_WIN)
 | 
			
		||||
  // On Windows, local UNC paths are rejected, as UNC path can be written in a
 | 
			
		||||
  // way that can bypass the blocklist.
 | 
			
		||||
  if (base::FeatureList::IsEnabled(
 | 
			
		||||
          features::kFileSystemAccessLocalUNCPathBlock) &&
 | 
			
		||||
      MaybeIsLocalUNCPath(path)) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // Add the hard-coded rules to the dynamic rules.
 | 
			
		||||
  for (auto const& [key, rule_path, type] :
 | 
			
		||||
       ChromeFileSystemAccessPermissionContext::kBlockedPaths) {
 | 
			
		||||
    if (key == ChromeFileSystemAccessPermissionContext::kNoBasePathKey) {
 | 
			
		||||
      rules.emplace_back(base::FilePath{rule_path}, type);
 | 
			
		||||
    } else if (base::FilePath path; base::PathService::Get(key, &path)) {
 | 
			
		||||
      rules.emplace_back(rule_path ? path.Append(rule_path) : path, type);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  base::FilePath nearest_ancestor;
 | 
			
		||||
  BlockType nearest_ancestor_block_type = BlockType::kDontBlockChildren;
 | 
			
		||||
  for (const auto& block : rules) {
 | 
			
		||||
    if (path == block.path || path.IsParent(block.path)) {
 | 
			
		||||
      DLOG(INFO) << "Blocking access to " << path
 | 
			
		||||
                 << " because it is a parent of " << block.path;
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (block.path.IsParent(path) &&
 | 
			
		||||
        (nearest_ancestor.empty() || nearest_ancestor.IsParent(block.path))) {
 | 
			
		||||
      nearest_ancestor = block.path;
 | 
			
		||||
      nearest_ancestor_block_type = block.type;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // The path we're checking is not in a potentially blocked directory, or the
 | 
			
		||||
  // nearest ancestor does not block access to its children. Grant access.
 | 
			
		||||
  if (nearest_ancestor.empty() ||
 | 
			
		||||
      nearest_ancestor_block_type == BlockType::kDontBlockChildren) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // The path we're checking is a file, and the nearest ancestor only blocks
 | 
			
		||||
  // access to directories. Grant access.
 | 
			
		||||
  if (handle_type == HandleType::kFile &&
 | 
			
		||||
      nearest_ancestor_block_type == BlockType::kBlockNestedDirectories) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // The nearest ancestor blocks access to its children, so block access.
 | 
			
		||||
  DLOG(INFO) << "Blocking access to " << path << " because it is inside "
 | 
			
		||||
             << nearest_ancestor;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
namespace electron {
 | 
			
		||||
 | 
			
		||||
class FileSystemAccessPermissionContext::PermissionGrantImpl
 | 
			
		||||
    : public content::FileSystemAccessPermissionGrant {
 | 
			
		||||
 public:
 | 
			
		||||
  PermissionGrantImpl(base::WeakPtr<FileSystemAccessPermissionContext> context,
 | 
			
		||||
                      const url::Origin& origin,
 | 
			
		||||
                      const base::FilePath& path,
 | 
			
		||||
                      HandleType handle_type,
 | 
			
		||||
                      GrantType type,
 | 
			
		||||
                      UserAction user_action)
 | 
			
		||||
      : context_{std::move(context)},
 | 
			
		||||
        origin_{origin},
 | 
			
		||||
        handle_type_{handle_type},
 | 
			
		||||
        type_{type},
 | 
			
		||||
        path_{path} {}
 | 
			
		||||
 | 
			
		||||
  // FileSystemAccessPermissionGrant:
 | 
			
		||||
  PermissionStatus GetStatus() override {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
    return status_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  base::FilePath GetPath() override {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
    return path_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RequestPermission(
 | 
			
		||||
      content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
      UserActivationState user_activation_state,
 | 
			
		||||
      base::OnceCallback<void(PermissionRequestOutcome)> callback) override {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
    // Check if a permission request has already been processed previously. This
 | 
			
		||||
    // check is done first because we don't want to reset the status of a
 | 
			
		||||
    // permission if it has already been granted.
 | 
			
		||||
    if (GetStatus() != PermissionStatus::ASK || !context_) {
 | 
			
		||||
      if (GetStatus() == PermissionStatus::GRANTED) {
 | 
			
		||||
        SetStatus(PermissionStatus::GRANTED);
 | 
			
		||||
      }
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kRequestAborted);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(frame_id);
 | 
			
		||||
    if (!rfh) {
 | 
			
		||||
      // Requested from a no longer valid RenderFrameHost.
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Don't request permission  for an inactive RenderFrameHost as the
 | 
			
		||||
    // page might not distinguish properly between user denying the permission
 | 
			
		||||
    // and automatic rejection.
 | 
			
		||||
    if (rfh->IsInactiveAndDisallowActivation(
 | 
			
		||||
            content::DisallowActivationReasonId::
 | 
			
		||||
                kFileSystemAccessPermissionRequest)) {
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We don't allow file system access from fenced frames.
 | 
			
		||||
    if (rfh->IsNestedWithinFencedFrame()) {
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (user_activation_state == UserActivationState::kRequired &&
 | 
			
		||||
        !rfh->HasTransientUserActivation()) {
 | 
			
		||||
      // No permission prompts without user activation.
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kNoUserActivation);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (content::WebContents::FromRenderFrameHost(rfh) == nullptr) {
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto origin = rfh->GetLastCommittedOrigin().GetURL();
 | 
			
		||||
    if (url::Origin::Create(origin) != origin_) {
 | 
			
		||||
      // Third party iframes are not allowed to request more permissions.
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kThirdPartyContext);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto* permission_manager =
 | 
			
		||||
        static_cast<electron::ElectronPermissionManager*>(
 | 
			
		||||
            context_->browser_context()->GetPermissionControllerDelegate());
 | 
			
		||||
    if (!permission_manager) {
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kRequestAborted);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blink::PermissionType type = static_cast<blink::PermissionType>(
 | 
			
		||||
        electron::WebContentsPermissionHelper::PermissionType::FILE_SYSTEM);
 | 
			
		||||
 | 
			
		||||
    base::Value::Dict details;
 | 
			
		||||
    details.Set("filePath", base::FilePathToValue(path_));
 | 
			
		||||
    details.Set("isDirectory", handle_type_ == HandleType::kDirectory);
 | 
			
		||||
    details.Set("fileAccessType",
 | 
			
		||||
                type_ == GrantType::kWrite ? "writable" : "readable");
 | 
			
		||||
 | 
			
		||||
    permission_manager->RequestPermissionWithDetails(
 | 
			
		||||
        type, rfh, origin, false, std::move(details),
 | 
			
		||||
        base::BindOnce(&PermissionGrantImpl::OnPermissionRequestResult, this,
 | 
			
		||||
                       std::move(callback)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const url::Origin& origin() const {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
    return origin_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  HandleType handle_type() const {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
    return handle_type_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  GrantType type() const {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
    return type_;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void SetStatus(PermissionStatus new_status) {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
    auto permission_changed = status_ != new_status;
 | 
			
		||||
    status_ = new_status;
 | 
			
		||||
 | 
			
		||||
    if (permission_changed) {
 | 
			
		||||
      NotifyPermissionStatusChanged();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void UpdateGrantPath(
 | 
			
		||||
      std::map<base::FilePath, PermissionGrantImpl*>& grants,
 | 
			
		||||
      const base::FilePath& old_path,
 | 
			
		||||
      const base::FilePath& new_path) {
 | 
			
		||||
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
 | 
			
		||||
    auto entry_it = base::ranges::find_if(
 | 
			
		||||
        grants,
 | 
			
		||||
        [&old_path](const auto& entry) { return entry.first == old_path; });
 | 
			
		||||
 | 
			
		||||
    if (entry_it == grants.end()) {
 | 
			
		||||
      // There must be an entry for an ancestor of this entry. Nothing to do
 | 
			
		||||
      // here.
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DCHECK_EQ(entry_it->second->GetStatus(), PermissionStatus::GRANTED);
 | 
			
		||||
 | 
			
		||||
    auto* const grant_impl = entry_it->second;
 | 
			
		||||
    grant_impl->SetPath(new_path);
 | 
			
		||||
 | 
			
		||||
    // Update the permission grant's key in the map of active permissions.
 | 
			
		||||
    grants.erase(entry_it);
 | 
			
		||||
    grants.emplace(new_path, grant_impl);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  ~PermissionGrantImpl() override {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
    if (context_) {
 | 
			
		||||
      context_->PermissionGrantDestroyed(this);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void OnPermissionRequestResult(
 | 
			
		||||
      base::OnceCallback<void(PermissionRequestOutcome)> callback,
 | 
			
		||||
      blink::mojom::PermissionStatus status) {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
    if (status == blink::mojom::PermissionStatus::GRANTED) {
 | 
			
		||||
      SetStatus(PermissionStatus::GRANTED);
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kUserGranted);
 | 
			
		||||
    } else {
 | 
			
		||||
      SetStatus(PermissionStatus::DENIED);
 | 
			
		||||
      std::move(callback).Run(PermissionRequestOutcome::kUserDenied);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void SetPath(const base::FilePath& new_path) {
 | 
			
		||||
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
    if (path_ == new_path)
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    path_ = new_path;
 | 
			
		||||
    NotifyPermissionStatusChanged();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SEQUENCE_CHECKER(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  base::WeakPtr<FileSystemAccessPermissionContext> const context_;
 | 
			
		||||
  const url::Origin origin_;
 | 
			
		||||
  const HandleType handle_type_;
 | 
			
		||||
  const GrantType type_;
 | 
			
		||||
  base::FilePath path_;
 | 
			
		||||
 | 
			
		||||
  // This member should only be updated via SetStatus().
 | 
			
		||||
  PermissionStatus status_ = PermissionStatus::ASK;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct FileSystemAccessPermissionContext::OriginState {
 | 
			
		||||
  // Raw pointers, owned collectively by all the handles that reference this
 | 
			
		||||
  // grant. When last reference goes away this state is cleared as well by
 | 
			
		||||
  // PermissionGrantDestroyed().
 | 
			
		||||
  std::map<base::FilePath, PermissionGrantImpl*> read_grants;
 | 
			
		||||
  std::map<base::FilePath, PermissionGrantImpl*> write_grants;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
FileSystemAccessPermissionContext::FileSystemAccessPermissionContext(
 | 
			
		||||
    content::BrowserContext* browser_context)
 | 
			
		||||
    : browser_context_(browser_context) {
 | 
			
		||||
  DETACH_FROM_SEQUENCE(sequence_checker_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileSystemAccessPermissionContext::~FileSystemAccessPermissionContext() =
 | 
			
		||||
    default;
 | 
			
		||||
 | 
			
		||||
scoped_refptr<content::FileSystemAccessPermissionGrant>
 | 
			
		||||
FileSystemAccessPermissionContext::GetReadPermissionGrant(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const base::FilePath& path,
 | 
			
		||||
    HandleType handle_type,
 | 
			
		||||
    UserAction user_action) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  // operator[] might insert a new OriginState in |active_permissions_map_|,
 | 
			
		||||
  // but that is exactly what we want.
 | 
			
		||||
  auto& origin_state = active_permissions_map_[origin];
 | 
			
		||||
  auto*& existing_grant = origin_state.read_grants[path];
 | 
			
		||||
  scoped_refptr<PermissionGrantImpl> new_grant;
 | 
			
		||||
 | 
			
		||||
  if (existing_grant && existing_grant->handle_type() != handle_type) {
 | 
			
		||||
    // |path| changed from being a directory to being a file or vice versa,
 | 
			
		||||
    // don't just re-use the existing grant but revoke the old grant before
 | 
			
		||||
    // creating a new grant.
 | 
			
		||||
    existing_grant->SetStatus(PermissionStatus::DENIED);
 | 
			
		||||
    existing_grant = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!existing_grant) {
 | 
			
		||||
    new_grant = base::MakeRefCounted<PermissionGrantImpl>(
 | 
			
		||||
        weak_factory_.GetWeakPtr(), origin, path, handle_type, GrantType::kRead,
 | 
			
		||||
        user_action);
 | 
			
		||||
    existing_grant = new_grant.get();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If a parent directory is already readable this new grant should also be
 | 
			
		||||
  // readable.
 | 
			
		||||
  if (new_grant &&
 | 
			
		||||
      AncestorHasActivePermission(origin, path, GrantType::kRead)) {
 | 
			
		||||
    existing_grant->SetStatus(PermissionStatus::GRANTED);
 | 
			
		||||
  } else {
 | 
			
		||||
    switch (user_action) {
 | 
			
		||||
      case UserAction::kOpen:
 | 
			
		||||
      case UserAction::kSave:
 | 
			
		||||
        // Open and Save dialog only grant read access for individual files.
 | 
			
		||||
        if (handle_type == HandleType::kDirectory) {
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
        [[fallthrough]];
 | 
			
		||||
      case UserAction::kDragAndDrop:
 | 
			
		||||
        // Drag&drop grants read access for all handles.
 | 
			
		||||
        existing_grant->SetStatus(PermissionStatus::GRANTED);
 | 
			
		||||
        break;
 | 
			
		||||
      case UserAction::kLoadFromStorage:
 | 
			
		||||
      case UserAction::kNone:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return existing_grant;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
scoped_refptr<content::FileSystemAccessPermissionGrant>
 | 
			
		||||
FileSystemAccessPermissionContext::GetWritePermissionGrant(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const base::FilePath& path,
 | 
			
		||||
    HandleType handle_type,
 | 
			
		||||
    UserAction user_action) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  // operator[] might insert a new OriginState in |active_permissions_map_|,
 | 
			
		||||
  // but that is exactly what we want.
 | 
			
		||||
  auto& origin_state = active_permissions_map_[origin];
 | 
			
		||||
  auto*& existing_grant = origin_state.write_grants[path];
 | 
			
		||||
  scoped_refptr<PermissionGrantImpl> new_grant;
 | 
			
		||||
 | 
			
		||||
  if (existing_grant && existing_grant->handle_type() != handle_type) {
 | 
			
		||||
    // |path| changed from being a directory to being a file or vice versa,
 | 
			
		||||
    // don't just re-use the existing grant but revoke the old grant before
 | 
			
		||||
    // creating a new grant.
 | 
			
		||||
    existing_grant->SetStatus(PermissionStatus::DENIED);
 | 
			
		||||
    existing_grant = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!existing_grant) {
 | 
			
		||||
    new_grant = base::MakeRefCounted<PermissionGrantImpl>(
 | 
			
		||||
        weak_factory_.GetWeakPtr(), origin, path, handle_type,
 | 
			
		||||
        GrantType::kWrite, user_action);
 | 
			
		||||
    existing_grant = new_grant.get();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If a parent directory is already writable this new grant should also be
 | 
			
		||||
  // writable.
 | 
			
		||||
  if (new_grant &&
 | 
			
		||||
      AncestorHasActivePermission(origin, path, GrantType::kWrite)) {
 | 
			
		||||
    existing_grant->SetStatus(PermissionStatus::GRANTED);
 | 
			
		||||
  } else {
 | 
			
		||||
    switch (user_action) {
 | 
			
		||||
      case UserAction::kSave:
 | 
			
		||||
        // Only automatically grant write access for save dialogs.
 | 
			
		||||
        existing_grant->SetStatus(PermissionStatus::GRANTED);
 | 
			
		||||
        break;
 | 
			
		||||
      case UserAction::kOpen:
 | 
			
		||||
      case UserAction::kDragAndDrop:
 | 
			
		||||
      case UserAction::kLoadFromStorage:
 | 
			
		||||
      case UserAction::kNone:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return existing_grant;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool FileSystemAccessPermissionContext::CanObtainReadPermission(
 | 
			
		||||
    const url::Origin& origin) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool FileSystemAccessPermissionContext::CanObtainWritePermission(
 | 
			
		||||
    const url::Origin& origin) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::ConfirmSensitiveEntryAccess(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    PathType path_type,
 | 
			
		||||
    const base::FilePath& path,
 | 
			
		||||
    HandleType handle_type,
 | 
			
		||||
    UserAction user_action,
 | 
			
		||||
    content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
    base::OnceCallback<void(SensitiveEntryResult)> callback) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  auto after_blocklist_check_callback = base::BindOnce(
 | 
			
		||||
      &FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist,
 | 
			
		||||
      GetWeakPtr(), origin, path, handle_type, user_action, frame_id,
 | 
			
		||||
      std::move(callback));
 | 
			
		||||
  CheckPathAgainstBlocklist(path_type, path, handle_type,
 | 
			
		||||
                            std::move(after_blocklist_check_callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::CheckPathAgainstBlocklist(
 | 
			
		||||
    PathType path_type,
 | 
			
		||||
    const base::FilePath& path,
 | 
			
		||||
    HandleType handle_type,
 | 
			
		||||
    base::OnceCallback<void(bool)> callback) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  // TODO(https://crbug.com/1009970): Figure out what external paths should be
 | 
			
		||||
  // blocked. We could resolve the external path to a local path, and check for
 | 
			
		||||
  // blocked directories based on that, but that doesn't work well. Instead we
 | 
			
		||||
  // should have a separate Chrome OS only code path to block for example the
 | 
			
		||||
  // root of certain external file systems.
 | 
			
		||||
  if (path_type == PathType::kExternal) {
 | 
			
		||||
    std::move(callback).Run(/*should_block=*/false);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<BlockPathRule> extra_rules;
 | 
			
		||||
  extra_rules.emplace_back(browser_context_->GetPath().DirName(),
 | 
			
		||||
                           BlockType::kBlockAllChildren);
 | 
			
		||||
 | 
			
		||||
  base::ThreadPool::PostTaskAndReplyWithResult(
 | 
			
		||||
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
 | 
			
		||||
      base::BindOnce(&ShouldBlockAccessToPath, path, handle_type, extra_rules),
 | 
			
		||||
      std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::PerformAfterWriteChecks(
 | 
			
		||||
    std::unique_ptr<content::FileSystemAccessWriteItem> item,
 | 
			
		||||
    content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
    base::OnceCallback<void(AfterWriteCheckResult)> callback) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  std::move(callback).Run(AfterWriteCheckResult::kAllow);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const base::FilePath& path,
 | 
			
		||||
    HandleType handle_type,
 | 
			
		||||
    UserAction user_action,
 | 
			
		||||
    content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
    base::OnceCallback<void(SensitiveEntryResult)> callback,
 | 
			
		||||
    bool should_block) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  if (user_action == UserAction::kNone) {
 | 
			
		||||
    std::move(callback).Run(should_block ? SensitiveEntryResult::kAbort
 | 
			
		||||
                                         : SensitiveEntryResult::kAllowed);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Chromium opens a dialog here, but in Electron's case we log and abort.
 | 
			
		||||
  if (should_block) {
 | 
			
		||||
    LOG(INFO) << path.value()
 | 
			
		||||
              << " is blocked by the blocklis and cannot be accessed";
 | 
			
		||||
    std::move(callback).Run(SensitiveEntryResult::kAbort);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::move(callback).Run(SensitiveEntryResult::kAllowed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::SetLastPickedDirectory(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const std::string& id,
 | 
			
		||||
    const base::FilePath& path,
 | 
			
		||||
    const PathType type) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  LOG(INFO) << "NOTIMPLEMENTED SetLastPickedDirectory: " << path.value();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileSystemAccessPermissionContext::PathInfo
 | 
			
		||||
FileSystemAccessPermissionContext::GetLastPickedDirectory(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const std::string& id) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  LOG(INFO) << "NOTIMPLEMENTED GetLastPickedDirectory";
 | 
			
		||||
  return PathInfo();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
base::FilePath FileSystemAccessPermissionContext::GetWellKnownDirectoryPath(
 | 
			
		||||
    blink::mojom::WellKnownDirectory directory,
 | 
			
		||||
    const url::Origin& origin) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  int key = base::PATH_START;
 | 
			
		||||
  switch (directory) {
 | 
			
		||||
    case blink::mojom::WellKnownDirectory::kDirDesktop:
 | 
			
		||||
      key = base::DIR_USER_DESKTOP;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::WellKnownDirectory::kDirDocuments:
 | 
			
		||||
      key = chrome::DIR_USER_DOCUMENTS;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::WellKnownDirectory::kDirDownloads:
 | 
			
		||||
      key = chrome::DIR_DEFAULT_DOWNLOADS;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::WellKnownDirectory::kDirMusic:
 | 
			
		||||
      key = chrome::DIR_USER_MUSIC;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::WellKnownDirectory::kDirPictures:
 | 
			
		||||
      key = chrome::DIR_USER_PICTURES;
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::WellKnownDirectory::kDirVideos:
 | 
			
		||||
      key = chrome::DIR_USER_VIDEOS;
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  base::FilePath directory_path;
 | 
			
		||||
  base::PathService::Get(key, &directory_path);
 | 
			
		||||
  return directory_path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::u16string FileSystemAccessPermissionContext::GetPickerTitle(
 | 
			
		||||
    const blink::mojom::FilePickerOptionsPtr& options) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  // TODO(asully): Consider adding custom strings for invocations of the file
 | 
			
		||||
  // picker, as well. Returning the empty string will fall back to the platform
 | 
			
		||||
  // default for the given picker type.
 | 
			
		||||
  std::u16string title;
 | 
			
		||||
  switch (options->type_specific_options->which()) {
 | 
			
		||||
    case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
 | 
			
		||||
        kDirectoryPickerOptions:
 | 
			
		||||
      title = l10n_util::GetStringUTF16(
 | 
			
		||||
          options->type_specific_options->get_directory_picker_options()
 | 
			
		||||
                  ->request_writable
 | 
			
		||||
              ? IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_WRITABLE_DIRECTORY_TITLE
 | 
			
		||||
              : IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_READABLE_DIRECTORY_TITLE);
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
 | 
			
		||||
        kSaveFilePickerOptions:
 | 
			
		||||
      title = l10n_util::GetStringUTF16(
 | 
			
		||||
          IDS_FILE_SYSTEM_ACCESS_CHOOSER_OPEN_SAVE_FILE_TITLE);
 | 
			
		||||
      break;
 | 
			
		||||
    case blink::mojom::TypeSpecificFilePickerOptionsUnion::Tag::
 | 
			
		||||
        kOpenFilePickerOptions:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  return title;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::NotifyEntryMoved(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const base::FilePath& old_path,
 | 
			
		||||
    const base::FilePath& new_path) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  if (old_path == new_path) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto it = active_permissions_map_.find(origin);
 | 
			
		||||
  if (it != active_permissions_map_.end()) {
 | 
			
		||||
    PermissionGrantImpl::UpdateGrantPath(it->second.write_grants, old_path,
 | 
			
		||||
                                         new_path);
 | 
			
		||||
    PermissionGrantImpl::UpdateGrantPath(it->second.read_grants, old_path,
 | 
			
		||||
                                         new_path);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::OnFileCreatedFromShowSaveFilePicker(
 | 
			
		||||
    const GURL& file_picker_binding_context,
 | 
			
		||||
    const storage::FileSystemURL& url) {}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::CheckPathsAgainstEnterprisePolicy(
 | 
			
		||||
    std::vector<PathInfo> entries,
 | 
			
		||||
    content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
    EntriesAllowedByEnterprisePolicyCallback callback) {
 | 
			
		||||
  std::move(callback).Run(std::move(entries));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::RevokeGrant(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const base::FilePath& file_path) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  auto origin_it = active_permissions_map_.find(origin);
 | 
			
		||||
  if (origin_it != active_permissions_map_.end()) {
 | 
			
		||||
    OriginState& origin_state = origin_it->second;
 | 
			
		||||
    for (auto& grant : origin_state.read_grants) {
 | 
			
		||||
      if (file_path.empty() || grant.first == file_path) {
 | 
			
		||||
        grant.second->SetStatus(PermissionStatus::ASK);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (auto& grant : origin_state.write_grants) {
 | 
			
		||||
      if (file_path.empty() || grant.first == file_path) {
 | 
			
		||||
        grant.second->SetStatus(PermissionStatus::ASK);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool FileSystemAccessPermissionContext::OriginHasReadAccess(
 | 
			
		||||
    const url::Origin& origin) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  auto it = active_permissions_map_.find(origin);
 | 
			
		||||
  if (it != active_permissions_map_.end()) {
 | 
			
		||||
    return base::ranges::any_of(it->second.read_grants, [&](const auto& grant) {
 | 
			
		||||
      return grant.second->GetStatus() == PermissionStatus::GRANTED;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool FileSystemAccessPermissionContext::OriginHasWriteAccess(
 | 
			
		||||
    const url::Origin& origin) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
  auto it = active_permissions_map_.find(origin);
 | 
			
		||||
  if (it != active_permissions_map_.end()) {
 | 
			
		||||
    return base::ranges::any_of(
 | 
			
		||||
        it->second.write_grants, [&](const auto& grant) {
 | 
			
		||||
          return grant.second->GetStatus() == PermissionStatus::GRANTED;
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::CleanupPermissions(
 | 
			
		||||
    const url::Origin& origin) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  RevokeGrant(origin);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool FileSystemAccessPermissionContext::AncestorHasActivePermission(
 | 
			
		||||
    const url::Origin& origin,
 | 
			
		||||
    const base::FilePath& path,
 | 
			
		||||
    GrantType grant_type) const {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  auto it = active_permissions_map_.find(origin);
 | 
			
		||||
  if (it == active_permissions_map_.end()) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  const auto& relevant_grants = grant_type == GrantType::kWrite
 | 
			
		||||
                                    ? it->second.write_grants
 | 
			
		||||
                                    : it->second.read_grants;
 | 
			
		||||
  if (relevant_grants.empty()) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Permissions are inherited from the closest ancestor.
 | 
			
		||||
  for (base::FilePath parent = path.DirName(); parent != parent.DirName();
 | 
			
		||||
       parent = parent.DirName()) {
 | 
			
		||||
    auto i = relevant_grants.find(parent);
 | 
			
		||||
    if (i != relevant_grants.end() && i->second &&
 | 
			
		||||
        i->second->GetStatus() == PermissionStatus::GRANTED) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileSystemAccessPermissionContext::PermissionGrantDestroyed(
 | 
			
		||||
    PermissionGrantImpl* grant) {
 | 
			
		||||
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 | 
			
		||||
  auto it = active_permissions_map_.find(grant->origin());
 | 
			
		||||
  if (it == active_permissions_map_.end()) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto& grants = grant->type() == GrantType::kRead ? it->second.read_grants
 | 
			
		||||
                                                   : it->second.write_grants;
 | 
			
		||||
  auto grant_it = grants.find(grant->GetPath());
 | 
			
		||||
  // Any non-denied permission grants should have still been in our grants
 | 
			
		||||
  // list. If this invariant is violated we would have permissions that might
 | 
			
		||||
  // be granted but won't be visible in any UI because the permission context
 | 
			
		||||
  // isn't tracking them anymore.
 | 
			
		||||
  if (grant_it == grants.end()) {
 | 
			
		||||
    DCHECK_EQ(PermissionStatus::DENIED, grant->GetStatus());
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // The grant in |grants| for this path might have been replaced with a
 | 
			
		||||
  // different grant. Only erase if it actually matches the grant that was
 | 
			
		||||
  // destroyed.
 | 
			
		||||
  if (grant_it->second == grant) {
 | 
			
		||||
    grants.erase(grant_it);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
base::WeakPtr<FileSystemAccessPermissionContext>
 | 
			
		||||
FileSystemAccessPermissionContext::GetWeakPtr() {
 | 
			
		||||
  return weak_factory_.GetWeakPtr();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace electron
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,154 @@
 | 
			
		|||
// Copyright (c) 2024 Microsoft, GmbH
 | 
			
		||||
// Use of this source code is governed by the MIT license that can be
 | 
			
		||||
// found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
#ifndef ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_ELECTRON_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
 | 
			
		||||
#define ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_ELECTRON_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
 | 
			
		||||
 | 
			
		||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "base/functional/callback.h"
 | 
			
		||||
#include "base/memory/weak_ptr.h"
 | 
			
		||||
#include "components/keyed_service/core/keyed_service.h"
 | 
			
		||||
#include "content/public/browser/file_system_access_permission_context.h"
 | 
			
		||||
 | 
			
		||||
class GURL;
 | 
			
		||||
 | 
			
		||||
namespace base {
 | 
			
		||||
class FilePath;
 | 
			
		||||
}  // namespace base
 | 
			
		||||
 | 
			
		||||
namespace storage {
 | 
			
		||||
class FileSystemURL;
 | 
			
		||||
}  // namespace storage
 | 
			
		||||
 | 
			
		||||
namespace electron {
 | 
			
		||||
 | 
			
		||||
class FileSystemAccessPermissionContext
 | 
			
		||||
    : public KeyedService,
 | 
			
		||||
      public content::FileSystemAccessPermissionContext {
 | 
			
		||||
 public:
 | 
			
		||||
  enum class GrantType { kRead, kWrite };
 | 
			
		||||
 | 
			
		||||
  explicit FileSystemAccessPermissionContext(
 | 
			
		||||
      content::BrowserContext* browser_context);
 | 
			
		||||
  FileSystemAccessPermissionContext(const FileSystemAccessPermissionContext&) =
 | 
			
		||||
      delete;
 | 
			
		||||
  FileSystemAccessPermissionContext& operator=(
 | 
			
		||||
      const FileSystemAccessPermissionContext&) = delete;
 | 
			
		||||
  ~FileSystemAccessPermissionContext() override;
 | 
			
		||||
 | 
			
		||||
  // content::FileSystemAccessPermissionContext:
 | 
			
		||||
  scoped_refptr<content::FileSystemAccessPermissionGrant>
 | 
			
		||||
  GetReadPermissionGrant(const url::Origin& origin,
 | 
			
		||||
                         const base::FilePath& path,
 | 
			
		||||
                         HandleType handle_type,
 | 
			
		||||
                         UserAction user_action) override;
 | 
			
		||||
 | 
			
		||||
  scoped_refptr<content::FileSystemAccessPermissionGrant>
 | 
			
		||||
  GetWritePermissionGrant(const url::Origin& origin,
 | 
			
		||||
                          const base::FilePath& path,
 | 
			
		||||
                          HandleType handle_type,
 | 
			
		||||
                          UserAction user_action) override;
 | 
			
		||||
 | 
			
		||||
  void ConfirmSensitiveEntryAccess(
 | 
			
		||||
      const url::Origin& origin,
 | 
			
		||||
      PathType path_type,
 | 
			
		||||
      const base::FilePath& path,
 | 
			
		||||
      HandleType handle_type,
 | 
			
		||||
      UserAction user_action,
 | 
			
		||||
      content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
      base::OnceCallback<void(SensitiveEntryResult)> callback) override;
 | 
			
		||||
 | 
			
		||||
  void PerformAfterWriteChecks(
 | 
			
		||||
      std::unique_ptr<content::FileSystemAccessWriteItem> item,
 | 
			
		||||
      content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
      base::OnceCallback<void(AfterWriteCheckResult)> callback) override;
 | 
			
		||||
 | 
			
		||||
  bool CanObtainReadPermission(const url::Origin& origin) override;
 | 
			
		||||
  bool CanObtainWritePermission(const url::Origin& origin) override;
 | 
			
		||||
 | 
			
		||||
  void SetLastPickedDirectory(const url::Origin& origin,
 | 
			
		||||
                              const std::string& id,
 | 
			
		||||
                              const base::FilePath& path,
 | 
			
		||||
                              const PathType type) override;
 | 
			
		||||
 | 
			
		||||
  PathInfo GetLastPickedDirectory(const url::Origin& origin,
 | 
			
		||||
                                  const std::string& id) override;
 | 
			
		||||
 | 
			
		||||
  base::FilePath GetWellKnownDirectoryPath(
 | 
			
		||||
      blink::mojom::WellKnownDirectory directory,
 | 
			
		||||
      const url::Origin& origin) override;
 | 
			
		||||
 | 
			
		||||
  std::u16string GetPickerTitle(
 | 
			
		||||
      const blink::mojom::FilePickerOptionsPtr& options) override;
 | 
			
		||||
 | 
			
		||||
  void NotifyEntryMoved(const url::Origin& origin,
 | 
			
		||||
                        const base::FilePath& old_path,
 | 
			
		||||
                        const base::FilePath& new_path) override;
 | 
			
		||||
 | 
			
		||||
  void OnFileCreatedFromShowSaveFilePicker(
 | 
			
		||||
      const GURL& file_picker_binding_context,
 | 
			
		||||
      const storage::FileSystemURL& url) override;
 | 
			
		||||
 | 
			
		||||
  void CheckPathsAgainstEnterprisePolicy(
 | 
			
		||||
      std::vector<PathInfo> entries,
 | 
			
		||||
      content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
      EntriesAllowedByEnterprisePolicyCallback callback) override;
 | 
			
		||||
 | 
			
		||||
  enum class Access { kRead, kWrite, kReadWrite };
 | 
			
		||||
 | 
			
		||||
  enum class RequestType { kNewPermission, kRestorePermissions };
 | 
			
		||||
 | 
			
		||||
  void RevokeGrant(const url::Origin& origin,
 | 
			
		||||
                   const base::FilePath& file_path = base::FilePath());
 | 
			
		||||
 | 
			
		||||
  bool OriginHasReadAccess(const url::Origin& origin);
 | 
			
		||||
  bool OriginHasWriteAccess(const url::Origin& origin);
 | 
			
		||||
 | 
			
		||||
  content::BrowserContext* browser_context() const { return browser_context_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  SEQUENCE_CHECKER(sequence_checker_);
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  class PermissionGrantImpl;
 | 
			
		||||
 | 
			
		||||
  void PermissionGrantDestroyed(PermissionGrantImpl* grant);
 | 
			
		||||
 | 
			
		||||
  void CheckPathAgainstBlocklist(PathType path_type,
 | 
			
		||||
                                 const base::FilePath& path,
 | 
			
		||||
                                 HandleType handle_type,
 | 
			
		||||
                                 base::OnceCallback<void(bool)> callback);
 | 
			
		||||
  void DidCheckPathAgainstBlocklist(
 | 
			
		||||
      const url::Origin& origin,
 | 
			
		||||
      const base::FilePath& path,
 | 
			
		||||
      HandleType handle_type,
 | 
			
		||||
      UserAction user_action,
 | 
			
		||||
      content::GlobalRenderFrameHostId frame_id,
 | 
			
		||||
      base::OnceCallback<void(SensitiveEntryResult)> callback,
 | 
			
		||||
      bool should_block);
 | 
			
		||||
 | 
			
		||||
  void CleanupPermissions(const url::Origin& origin);
 | 
			
		||||
 | 
			
		||||
  bool AncestorHasActivePermission(const url::Origin& origin,
 | 
			
		||||
                                   const base::FilePath& path,
 | 
			
		||||
                                   GrantType grant_type) const;
 | 
			
		||||
 | 
			
		||||
  base::WeakPtr<FileSystemAccessPermissionContext> GetWeakPtr();
 | 
			
		||||
 | 
			
		||||
  const raw_ptr<content::BrowserContext, DanglingUntriaged> browser_context_;
 | 
			
		||||
 | 
			
		||||
  struct OriginState;
 | 
			
		||||
  std::map<url::Origin, OriginState> active_permissions_map_;
 | 
			
		||||
 | 
			
		||||
  base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace electron
 | 
			
		||||
 | 
			
		||||
#endif  // ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
// Copyright (c) 2024 Microsoft, GmbH
 | 
			
		||||
// Use of this source code is governed by the MIT license that can be
 | 
			
		||||
// found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
#include "shell/browser/file_system_access/file_system_access_permission_context_factory.h"
 | 
			
		||||
 | 
			
		||||
#include "base/functional/bind.h"
 | 
			
		||||
#include "base/memory/ptr_util.h"
 | 
			
		||||
#include "base/no_destructor.h"
 | 
			
		||||
#include "components/keyed_service/content/browser_context_dependency_manager.h"
 | 
			
		||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
 | 
			
		||||
 | 
			
		||||
namespace electron {
 | 
			
		||||
 | 
			
		||||
// static
 | 
			
		||||
electron::FileSystemAccessPermissionContext*
 | 
			
		||||
FileSystemAccessPermissionContextFactory::GetForBrowserContext(
 | 
			
		||||
    content::BrowserContext* context) {
 | 
			
		||||
  return static_cast<electron::FileSystemAccessPermissionContext*>(
 | 
			
		||||
      GetInstance()->GetServiceForBrowserContext(context, true));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// static
 | 
			
		||||
FileSystemAccessPermissionContextFactory*
 | 
			
		||||
FileSystemAccessPermissionContextFactory::GetInstance() {
 | 
			
		||||
  static base::NoDestructor<FileSystemAccessPermissionContextFactory> instance;
 | 
			
		||||
  return instance.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FileSystemAccessPermissionContextFactory::
 | 
			
		||||
    FileSystemAccessPermissionContextFactory()
 | 
			
		||||
    : BrowserContextKeyedServiceFactory(
 | 
			
		||||
          "FileSystemAccessPermissionContext",
 | 
			
		||||
          BrowserContextDependencyManager::GetInstance()) {}
 | 
			
		||||
 | 
			
		||||
FileSystemAccessPermissionContextFactory::
 | 
			
		||||
    ~FileSystemAccessPermissionContextFactory() = default;
 | 
			
		||||
 | 
			
		||||
// static
 | 
			
		||||
KeyedService* FileSystemAccessPermissionContextFactory::BuildServiceInstanceFor(
 | 
			
		||||
    content::BrowserContext* context) const {
 | 
			
		||||
  return BuildInstanceFor(context).release();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::unique_ptr<KeyedService>
 | 
			
		||||
FileSystemAccessPermissionContextFactory::BuildInstanceFor(
 | 
			
		||||
    content::BrowserContext* context) {
 | 
			
		||||
  return std::make_unique<FileSystemAccessPermissionContext>(context);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace electron
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
// Copyright (c) 2024 Microsoft, GmbH
 | 
			
		||||
// Use of this source code is governed by the MIT license that can be
 | 
			
		||||
// found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
#ifndef ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_H_
 | 
			
		||||
#define ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_H_
 | 
			
		||||
 | 
			
		||||
#include "base/no_destructor.h"
 | 
			
		||||
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 | 
			
		||||
#include "shell/browser/file_system_access/file_system_access_permission_context.h"
 | 
			
		||||
 | 
			
		||||
namespace electron {
 | 
			
		||||
 | 
			
		||||
class FileSystemAccessPermissionContextFactory
 | 
			
		||||
    : public BrowserContextKeyedServiceFactory {
 | 
			
		||||
 public:
 | 
			
		||||
  static FileSystemAccessPermissionContext* GetForBrowserContext(
 | 
			
		||||
      content::BrowserContext* context);
 | 
			
		||||
  static FileSystemAccessPermissionContextFactory* GetInstance();
 | 
			
		||||
 | 
			
		||||
  static std::unique_ptr<KeyedService> BuildInstanceFor(
 | 
			
		||||
      content::BrowserContext* context);
 | 
			
		||||
 | 
			
		||||
  FileSystemAccessPermissionContextFactory(
 | 
			
		||||
      const FileSystemAccessPermissionContextFactory&) = delete;
 | 
			
		||||
  FileSystemAccessPermissionContextFactory& operator=(
 | 
			
		||||
      const FileSystemAccessPermissionContextFactory&) = delete;
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  friend class base::NoDestructor<FileSystemAccessPermissionContextFactory>;
 | 
			
		||||
 | 
			
		||||
  FileSystemAccessPermissionContextFactory();
 | 
			
		||||
  ~FileSystemAccessPermissionContextFactory() override;
 | 
			
		||||
 | 
			
		||||
  // BrowserContextKeyedServiceFactory:
 | 
			
		||||
  KeyedService* BuildServiceInstanceFor(
 | 
			
		||||
      content::BrowserContext* context) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace electron
 | 
			
		||||
 | 
			
		||||
#endif  // ELECTRON_SHELL_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,9 @@ class WebContentsPermissionHelper
 | 
			
		|||
    OPEN_EXTERNAL,
 | 
			
		||||
    SERIAL,
 | 
			
		||||
    HID,
 | 
			
		||||
    USB
 | 
			
		||||
    USB,
 | 
			
		||||
    KEYBOARD_LOCK,
 | 
			
		||||
    FILE_SYSTEM
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Asynchronous Requests
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@
 | 
			
		|||
#include "third_party/skia/include/core/SkImageInfo.h"
 | 
			
		||||
#include "third_party/skia/include/core/SkPixmap.h"
 | 
			
		||||
#include "ui/base/clipboard/clipboard_format_type.h"
 | 
			
		||||
#include "ui/base/clipboard/file_info.h"
 | 
			
		||||
#include "ui/base/clipboard/scoped_clipboard_writer.h"
 | 
			
		||||
#include "ui/gfx/codec/png_codec.h"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -274,6 +275,17 @@ void Clipboard::Clear(gin_helper::Arguments* args) {
 | 
			
		|||
  ui::Clipboard::GetForCurrentThread()->Clear(GetClipboardBuffer(args));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This exists for testing purposes ONLY.
 | 
			
		||||
void Clipboard::WriteFilesForTesting(const std::vector<base::FilePath>& files) {
 | 
			
		||||
  std::vector<ui::FileInfo> file_infos;
 | 
			
		||||
  for (const auto& file : files) {
 | 
			
		||||
    file_infos.emplace_back(ui::FileInfo(ui::FileInfo(file, file.BaseName())));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste);
 | 
			
		||||
  writer.WriteFilenames(ui::FileInfosToURIList(file_infos));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace electron::api
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
| 
						 | 
				
			
			@ -302,6 +314,8 @@ void Initialize(v8::Local<v8::Object> exports,
 | 
			
		|||
  dict.SetMethod("writeFindText", &electron::api::Clipboard::WriteFindText);
 | 
			
		||||
  dict.SetMethod("readBuffer", &electron::api::Clipboard::ReadBuffer);
 | 
			
		||||
  dict.SetMethod("writeBuffer", &electron::api::Clipboard::WriteBuffer);
 | 
			
		||||
  dict.SetMethod("_writeFilesForTesting",
 | 
			
		||||
                 &electron::api::Clipboard::WriteFilesForTesting);
 | 
			
		||||
  dict.SetMethod("clear", &electron::api::Clipboard::Clear);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@
 | 
			
		|||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "shell/common/gin_converters/file_path_converter.h"
 | 
			
		||||
#include "ui/base/clipboard/clipboard.h"
 | 
			
		||||
#include "ui/gfx/image/image.h"
 | 
			
		||||
#include "v8/include/v8.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +64,8 @@ class Clipboard {
 | 
			
		|||
  static void WriteBuffer(const std::string& format_string,
 | 
			
		||||
                          const v8::Local<v8::Value> buffer,
 | 
			
		||||
                          gin_helper::Arguments* args);
 | 
			
		||||
 | 
			
		||||
  static void WriteFilesForTesting(const std::vector<base::FilePath>& files);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace electron::api
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -229,6 +229,8 @@ v8::Local<v8::Value> Converter<blink::PermissionType>::ToV8(
 | 
			
		|||
      return StringToV8(isolate, "hid");
 | 
			
		||||
    case PermissionType::USB:
 | 
			
		||||
      return StringToV8(isolate, "usb");
 | 
			
		||||
    case PermissionType::FILE_SYSTEM:
 | 
			
		||||
      return StringToV8(isolate, "fileSystem");
 | 
			
		||||
    default:
 | 
			
		||||
      return StringToV8(isolate, "unknown");
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import { expect } from 'chai';
 | 
			
		||||
import { BrowserWindow, WebContents, webFrameMain, session, ipcMain, app, protocol, webContents, dialog, MessageBoxOptions } from 'electron/main';
 | 
			
		||||
import { clipboard } from 'electron/common';
 | 
			
		||||
import { closeAllWindows } from './lib/window-helpers';
 | 
			
		||||
import * as https from 'node:https';
 | 
			
		||||
import * as http from 'node:http';
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +14,7 @@ import { PipeTransport } from './pipe-transport';
 | 
			
		|||
import * as ws from 'ws';
 | 
			
		||||
import { setTimeout } from 'node:timers/promises';
 | 
			
		||||
import { AddressInfo } from 'node:net';
 | 
			
		||||
import { MediaAccessPermissionRequest } from 'electron';
 | 
			
		||||
 | 
			
		||||
const features = process._linkedBinding('electron_common_features');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -846,6 +848,129 @@ describe('chromium features', () => {
 | 
			
		|||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('File System API,', () => {
 | 
			
		||||
    afterEach(closeAllWindows);
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
      session.defaultSession.setPermissionRequestHandler(null);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('allows access by default to reading an OPFS file', async () => {
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        show: false,
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          partition: 'file-system-spec',
 | 
			
		||||
          contextIsolation: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
 | 
			
		||||
      const result = await w.webContents.executeJavaScript(`
 | 
			
		||||
        new Promise(async (resolve, reject) => {
 | 
			
		||||
          const root = await navigator.storage.getDirectory();
 | 
			
		||||
          const fileHandle = await root.getFileHandle('test', { create: true });
 | 
			
		||||
          const { name, size } = await fileHandle.getFile();
 | 
			
		||||
          resolve({ name, size });
 | 
			
		||||
        }
 | 
			
		||||
      )`, true);
 | 
			
		||||
      expect(result).to.deep.equal({ name: 'test', size: 0 });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('fileHandle.queryPermission by default has permission to read and write to OPFS files', async () => {
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        show: false,
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          partition: 'file-system-spec',
 | 
			
		||||
          contextIsolation: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
 | 
			
		||||
      const status = await w.webContents.executeJavaScript(`
 | 
			
		||||
        new Promise(async (resolve, reject) => {
 | 
			
		||||
          const root = await navigator.storage.getDirectory();
 | 
			
		||||
          const fileHandle = await root.getFileHandle('test', { create: true });
 | 
			
		||||
          const status = await fileHandle.queryPermission({ mode: 'readwrite' });
 | 
			
		||||
          resolve(status);
 | 
			
		||||
        }
 | 
			
		||||
      )`, true);
 | 
			
		||||
      expect(status).to.equal('granted');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('fileHandle.requestPermission automatically grants permission to read and write to OPFS files', async () => {
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          partition: 'file-system-spec',
 | 
			
		||||
          contextIsolation: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
 | 
			
		||||
      const status = await w.webContents.executeJavaScript(`
 | 
			
		||||
        new Promise(async (resolve, reject) => {
 | 
			
		||||
          const root = await navigator.storage.getDirectory();
 | 
			
		||||
          const fileHandle = await root.getFileHandle('test', { create: true });
 | 
			
		||||
          const status = await fileHandle.requestPermission({ mode: 'readwrite' });
 | 
			
		||||
          resolve(status);
 | 
			
		||||
        }
 | 
			
		||||
      )`, true);
 | 
			
		||||
      expect(status).to.equal('granted');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('requests permission when trying to create a writable file handle', (done) => {
 | 
			
		||||
      const writablePath = path.join(fixturesPath, 'file-system', 'test-writable.html');
 | 
			
		||||
      const testFile = path.join(fixturesPath, 'file-system', 'test.txt');
 | 
			
		||||
 | 
			
		||||
      const w = new BrowserWindow({
 | 
			
		||||
        webPreferences: {
 | 
			
		||||
          nodeIntegration: true,
 | 
			
		||||
          contextIsolation: false,
 | 
			
		||||
          sandbox: false
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.webContents.session.setPermissionRequestHandler((wc, permission, callback, details) => {
 | 
			
		||||
        expect(permission).to.equal('fileSystem');
 | 
			
		||||
 | 
			
		||||
        const { href } = url.pathToFileURL(writablePath);
 | 
			
		||||
        expect(details).to.deep.equal({
 | 
			
		||||
          fileAccessType: 'writable',
 | 
			
		||||
          isDirectory: false,
 | 
			
		||||
          isMainFrame: true,
 | 
			
		||||
          filePath: testFile,
 | 
			
		||||
          requestingUrl: href
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        callback(true);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      ipcMain.once('did-create-file-handle', async () => {
 | 
			
		||||
        const result = await w.webContents.executeJavaScript(`
 | 
			
		||||
          new Promise((resolve, reject) => {
 | 
			
		||||
            try {
 | 
			
		||||
              const writable = fileHandle.createWritable();
 | 
			
		||||
              resolve(true);
 | 
			
		||||
            } catch {
 | 
			
		||||
              resolve(false);
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
        `, true);
 | 
			
		||||
        expect(result).to.be.true();
 | 
			
		||||
        done();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      w.loadFile(writablePath);
 | 
			
		||||
 | 
			
		||||
      w.webContents.once('did-finish-load', () => {
 | 
			
		||||
        // @ts-expect-error Undocumented testing method.
 | 
			
		||||
        clipboard._writeFilesForTesting([testFile]);
 | 
			
		||||
        w.webContents.paste();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('web workers', () => {
 | 
			
		||||
    let appProcess: ChildProcess.ChildProcessWithoutNullStreams | undefined;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1663,7 +1788,7 @@ describe('chromium features', () => {
 | 
			
		|||
    it('provides a securityOrigin to the request handler', async () => {
 | 
			
		||||
      session.defaultSession.setPermissionRequestHandler(
 | 
			
		||||
        (wc, permission, callback, details) => {
 | 
			
		||||
          if (details.securityOrigin !== undefined) {
 | 
			
		||||
          if ((details as MediaAccessPermissionRequest).securityOrigin !== undefined) {
 | 
			
		||||
            callback(true);
 | 
			
		||||
          } else {
 | 
			
		||||
            callback(false);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										26
									
								
								spec/fixtures/file-system/test-writable.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								spec/fixtures/file-system/test-writable.html
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="UTF-8">
 | 
			
		||||
  <title>Hello World!</title>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
  <script>
 | 
			
		||||
    const { ipcRenderer } = require('electron')
 | 
			
		||||
 | 
			
		||||
    let fileHandle = null;
 | 
			
		||||
    let sent = false;
 | 
			
		||||
    window.document.onpaste = async (event) => {
 | 
			
		||||
      const fileItem = event.clipboardData.items[0];
 | 
			
		||||
      fileHandle = await fileItem.getAsFileSystemHandle();
 | 
			
		||||
      if (!sent) {
 | 
			
		||||
        ipcRenderer.send('did-create-file-handle');
 | 
			
		||||
        sent = true;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  </script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										1
									
								
								spec/fixtures/file-system/test.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								spec/fixtures/file-system/test.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
hello world
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue