diff options
Diffstat (limited to 'src/core/file_system_access')
11 files changed, 1263 insertions, 0 deletions
diff --git a/src/core/file_system_access/file_system_access_permission_context_factory_qt.cpp b/src/core/file_system_access/file_system_access_permission_context_factory_qt.cpp new file mode 100644 index 000000000..51c8a8991 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_factory_qt.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "file_system_access_permission_context_factory_qt.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" + +#include <QtGlobal> + +namespace QtWebEngineCore { + +// static +FileSystemAccessPermissionContextQt * +FileSystemAccessPermissionContextFactoryQt::GetForProfile(content::BrowserContext *profile) +{ + return static_cast<FileSystemAccessPermissionContextQt *>( + GetInstance()->GetServiceForBrowserContext(profile, true)); +} + +// static +FileSystemAccessPermissionContextQt * +FileSystemAccessPermissionContextFactoryQt::GetForProfileIfExists(content::BrowserContext *profile) +{ + return static_cast<FileSystemAccessPermissionContextQt *>( + GetInstance()->GetServiceForBrowserContext(profile, true)); +} + +// static +FileSystemAccessPermissionContextFactoryQt * +FileSystemAccessPermissionContextFactoryQt::GetInstance() +{ + return base::Singleton<FileSystemAccessPermissionContextFactoryQt>::get(); +} + +FileSystemAccessPermissionContextFactoryQt::FileSystemAccessPermissionContextFactoryQt() + : BrowserContextKeyedServiceFactory("FileSystemAccessPermissionContext", + BrowserContextDependencyManager::GetInstance()) +{ +} + +FileSystemAccessPermissionContextFactoryQt::~FileSystemAccessPermissionContextFactoryQt() = default; + +content::BrowserContext *FileSystemAccessPermissionContextFactoryQt::GetBrowserContextToUse( + content::BrowserContext *context) const +{ + return context; +} + +KeyedService *FileSystemAccessPermissionContextFactoryQt::BuildServiceInstanceFor( + content::BrowserContext *context) const +{ + return new FileSystemAccessPermissionContextQt(context); +} + +void FileSystemAccessPermissionContextFactoryQt::BrowserContextShutdown( + content::BrowserContext *context) +{ + Q_UNUSED(context); +} + +} // namespace QtWebEngineCore diff --git a/src/core/file_system_access/file_system_access_permission_context_factory_qt.h b/src/core/file_system_access/file_system_access_permission_context_factory_qt.h new file mode 100644 index 000000000..1d98243d6 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_factory_qt.h @@ -0,0 +1,37 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_QT_H +#define FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_QT_H + +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +#include "file_system_access_permission_context_qt.h" + +namespace QtWebEngineCore { + +class FileSystemAccessPermissionContextFactoryQt : public BrowserContextKeyedServiceFactory +{ +public: + static FileSystemAccessPermissionContextQt *GetForProfile(content::BrowserContext *profile); + static FileSystemAccessPermissionContextQt * + GetForProfileIfExists(content::BrowserContext *profile); + static FileSystemAccessPermissionContextFactoryQt *GetInstance(); + +private: + friend struct base::DefaultSingletonTraits<FileSystemAccessPermissionContextFactoryQt>; + + FileSystemAccessPermissionContextFactoryQt(); + ~FileSystemAccessPermissionContextFactoryQt() override; + + // BrowserContextKeyedServiceFactory + content::BrowserContext * + GetBrowserContextToUse(content::BrowserContext *context) const override; + KeyedService *BuildServiceInstanceFor(content::BrowserContext *profile) const override; + void BrowserContextShutdown(content::BrowserContext *context) override; +}; + +} // namespace QtWebEngineCore + +#endif // FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_FACTORY_QT_H diff --git a/src/core/file_system_access/file_system_access_permission_context_qt.cpp b/src/core/file_system_access/file_system_access_permission_context_qt.cpp new file mode 100644 index 000000000..c290aab82 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_qt.cpp @@ -0,0 +1,479 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// This file is based on chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc: +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "file_system_access_permission_context_qt.h" + +#include "base/base_paths.h" +#include "base/path_service.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" +#include "chrome/common/chrome_paths.h" +#include "components/content_settings/core/common/content_settings_types.h" +#include "content/browser/web_contents/web_contents_impl.h" + +#include "file_system_access_permission_grant_qt.h" +#include "type_conversion.h" + +#include <QCoreApplication> +#include <QStandardPaths> + +namespace QtWebEngineCore { + +// 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, kDontBlockAppFolder }; + +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 }, + { base::DIR_MODULE, nullptr, kBlockAllChildren }, + { 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 defined(OS_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 }, + // Whitelist AppData\Local\Temp to make the default location of QDir::tempPath(), + // QTemporaryFile and QTemporaryDir working on Windows. + { base::DIR_LOCAL_APP_DATA, FILE_PATH_LITERAL("Temp"), kDontBlockAppFolder }, + { 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 defined(OS_MAC) + // Similar Mac specific blocks. + { base::DIR_APP_DATA, nullptr, kBlockAllChildren }, + { base::DIR_HOME, FILE_PATH_LITERAL("Library"), kBlockAllChildren }, + // Allow access to iCloud files. + { base::DIR_HOME, FILE_PATH_LITERAL("Library/Mobile Documents"), kDontBlockChildren }, +#endif +#if defined(OS_LINUX) || defined(OS_CHROMEOS) + // On Linux also block access to devices via /dev, as well as security + // sensitive data in /sys and /proc. + { kNoBasePathKey, FILE_PATH_LITERAL("/dev"), kBlockAllChildren }, + { kNoBasePathKey, FILE_PATH_LITERAL("/sys"), kBlockAllChildren }, + { kNoBasePathKey, FILE_PATH_LITERAL("/proc"), 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? +}; + +bool ShouldBlockAccessToPath(const base::FilePath &check_path, HandleType handle_type) +{ + DCHECK(!check_path.empty()); + DCHECK(check_path.IsAbsolute()); + + base::FilePath nearest_ancestor; + int nearest_ancestor_path_key = kNoBasePathKey; + BlockType nearest_ancestor_block_type = kDontBlockChildren; + for (const auto &block : kBlockedPaths) { + base::FilePath blocked_path; + if (block.base_path_key != kNoBasePathKey) { + if (!base::PathService::Get(block.base_path_key, &blocked_path)) + continue; + if (block.path) + blocked_path = blocked_path.Append(block.path); + } else { + DCHECK(block.path); + blocked_path = base::FilePath(block.path); + } + + if (check_path == blocked_path || check_path.IsParent(blocked_path)) { + VLOG(1) << "Blocking access to " << check_path << " because it is a parent of " + << blocked_path << " (" << block.base_path_key << ")"; + return true; + } + + if (blocked_path.IsParent(check_path) + && (nearest_ancestor.empty() || nearest_ancestor.IsParent(blocked_path))) { + nearest_ancestor = blocked_path; + nearest_ancestor_path_key = block.base_path_key; + 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 == 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 == kBlockNestedDirectories) + return false; + + if (nearest_ancestor_block_type == kDontBlockAppFolder) { + // Relative path from the nearest blocklisted ancestor + base::FilePath diff; + nearest_ancestor.AppendRelativePath(check_path, &diff); + + auto diff_components = diff.GetComponents(); + if (diff_components.size() > 0 && toQt(diff_components[0]).contains(QCoreApplication::applicationName())) { + // The relative path contains the application name. Grant access. + return false; + } + } + + // The nearest ancestor blocks access to its children, so block access. + VLOG(1) << "Blocking access to " << check_path << " because it is inside " << nearest_ancestor + << " (" << nearest_ancestor_path_key << ")"; + return true; +} + +struct FileSystemAccessPermissionContextQt::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, FileSystemAccessPermissionGrantQt *> read_grants; + std::map<base::FilePath, FileSystemAccessPermissionGrantQt *> write_grants; +}; + +FileSystemAccessPermissionContextQt::FileSystemAccessPermissionContextQt( + content::BrowserContext *context) + : m_profile(context) +{ +} + +FileSystemAccessPermissionContextQt::~FileSystemAccessPermissionContextQt() = default; + +scoped_refptr<content::FileSystemAccessPermissionGrant> +FileSystemAccessPermissionContextQt::GetReadPermissionGrant(const url::Origin &origin, + const base::FilePath &path, + HandleType handle_type, + UserAction user_action) +{ + auto &origin_state = m_origins[origin]; + auto *&existing_grant = origin_state.read_grants[path]; + scoped_refptr<FileSystemAccessPermissionGrantQt> new_grant; + + if (existing_grant && existing_grant->handleType() != 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(blink::mojom::PermissionStatus::DENIED); + existing_grant = nullptr; + } + + if (!existing_grant) { + new_grant = base::MakeRefCounted<FileSystemAccessPermissionGrantQt>( + m_weakFactory.GetWeakPtr(), origin, path, handle_type, GrantType::kRead); + 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(blink::mojom::PermissionStatus::GRANTED); + return existing_grant; + } + + 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; + Q_FALLTHROUGH(); + case UserAction::kDragAndDrop: + // Drag&drop grants read access for all handles. + existing_grant->SetStatus(blink::mojom::PermissionStatus::GRANTED); + break; + case UserAction::kLoadFromStorage: + break; + case UserAction::kNone: + Q_UNREACHABLE(); + } + + return existing_grant; +} + +scoped_refptr<content::FileSystemAccessPermissionGrant> +FileSystemAccessPermissionContextQt::GetWritePermissionGrant(const url::Origin &origin, + const base::FilePath &path, + HandleType handle_type, + UserAction user_action) +{ + auto &origin_state = m_origins[origin]; + auto *&existing_grant = origin_state.write_grants[path]; + scoped_refptr<FileSystemAccessPermissionGrantQt> new_grant; + + if (existing_grant && existing_grant->handleType() != 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(blink::mojom::PermissionStatus::DENIED); + existing_grant = nullptr; + } + + if (!existing_grant) { + new_grant = base::MakeRefCounted<FileSystemAccessPermissionGrantQt>( + m_weakFactory.GetWeakPtr(), origin, path, handle_type, GrantType::kWrite); + 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(blink::mojom::PermissionStatus::GRANTED); + return existing_grant; + } + + switch (user_action) { + case UserAction::kSave: + // Only automatically grant write access for save dialogs. + existing_grant->SetStatus(blink::mojom::PermissionStatus::GRANTED); + break; + case UserAction::kOpen: + case UserAction::kDragAndDrop: + case UserAction::kLoadFromStorage: + break; + case UserAction::kNone: + Q_UNREACHABLE(); + } + + return existing_grant; +} + +void FileSystemAccessPermissionContextQt::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) +{ + if (path_type == PathType::kExternal) { + std::move(callback).Run(SensitiveEntryResult::kAllowed); + return; + } + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, { base::MayBlock(), base::TaskPriority::USER_VISIBLE }, + base::BindOnce(&ShouldBlockAccessToPath, path, handle_type), + base::BindOnce(&FileSystemAccessPermissionContextQt::DidConfirmSensitiveDirectoryAccess, + m_weakFactory.GetWeakPtr(), origin, path, handle_type, user_action, frame_id, + std::move(callback))); +} + +void FileSystemAccessPermissionContextQt::PerformAfterWriteChecks( + std::unique_ptr<content::FileSystemAccessWriteItem> item, + content::GlobalRenderFrameHostId frame_id, + base::OnceCallback<void(AfterWriteCheckResult)> callback) +{ + Q_UNUSED(item); + Q_UNUSED(frame_id); + std::move(callback).Run(AfterWriteCheckResult::kAllow); +} + +bool FileSystemAccessPermissionContextQt::CanObtainReadPermission(const url::Origin &origin) +{ + Q_UNUSED(origin); + return true; +} + +bool FileSystemAccessPermissionContextQt::CanObtainWritePermission(const url::Origin &origin) +{ + Q_UNUSED(origin); + return true; +} + +void FileSystemAccessPermissionContextQt::SetLastPickedDirectory(const url::Origin &origin, + const std::string &id, + const base::FilePath &path, + const PathType type) +{ + Q_UNUSED(origin); + + FileSystemAccessPermissionContextQt::PathInfo info; + info.path = path; + info.type = type; + m_lastPickedDirectories.insert({ id, info }); +} + +FileSystemAccessPermissionContextQt::PathInfo +FileSystemAccessPermissionContextQt::GetLastPickedDirectory(const url::Origin &origin, + const std::string &id) +{ + Q_UNUSED(origin); + + return m_lastPickedDirectories.find(id) != m_lastPickedDirectories.end() + ? m_lastPickedDirectories[id] + : FileSystemAccessPermissionContextQt::PathInfo(); +} + +base::FilePath FileSystemAccessPermissionContextQt::GetWellKnownDirectoryPath( + blink::mojom::WellKnownDirectory directory, const url::Origin &origin) +{ + QStandardPaths::StandardLocation location = QStandardPaths::DocumentsLocation; + switch (directory) { + case blink::mojom::WellKnownDirectory::kDirDesktop: + location = QStandardPaths::DesktopLocation; + break; + case blink::mojom::WellKnownDirectory::kDirDocuments: + location = QStandardPaths::DocumentsLocation; + break; + case blink::mojom::WellKnownDirectory::kDirDownloads: + location = QStandardPaths::DownloadLocation; + break; + case blink::mojom::WellKnownDirectory::kDirMusic: + location = QStandardPaths::MusicLocation; + break; + case blink::mojom::WellKnownDirectory::kDirPictures: + location = QStandardPaths::PicturesLocation; + break; + case blink::mojom::WellKnownDirectory::kDirVideos: + location = QStandardPaths::MoviesLocation; + break; + } + + return toFilePath(QStandardPaths::writableLocation(location)); +} + +void FileSystemAccessPermissionContextQt::NavigatedAwayFromOrigin(const url::Origin &origin) +{ + // If the last top-level WebContents for an origin is closed (or is navigated to another + // origin), all the permissions for that origin will be revoked. + + auto it = m_origins.find(origin); + // If we have no permissions for the origin, there is nothing to do. + if (it == m_origins.end()) + return; + + std::vector<content::WebContentsImpl *> list = content::WebContentsImpl::GetAllWebContents(); + for (content::WebContentsImpl *web_contents : list) { + url::Origin web_contents_origin = url::Origin::Create(web_contents->GetLastCommittedURL()); + // Found a tab for this origin, so early exit and don't revoke grants. + if (web_contents_origin == origin) + return; + } + + OriginState &origin_state = it->second; + for (auto &grant : origin_state.read_grants) + grant.second->SetStatus(blink::mojom::PermissionStatus::ASK); + for (auto &grant : origin_state.write_grants) + grant.second->SetStatus(blink::mojom::PermissionStatus::ASK); +} + +void FileSystemAccessPermissionContextQt::DidConfirmSensitiveDirectoryAccess( + 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) +{ + Q_UNUSED(origin); + Q_UNUSED(path); + Q_UNUSED(handle_type); + Q_UNUSED(user_action); + Q_UNUSED(frame_id); + + if (should_block) + std::move(callback).Run(SensitiveEntryResult::kAbort); + else + std::move(callback).Run(SensitiveEntryResult::kAllowed); +} + +bool FileSystemAccessPermissionContextQt::AncestorHasActivePermission( + const url::Origin &origin, const base::FilePath &path, GrantType grant_type) const +{ + auto it = m_origins.find(origin); + if (it == m_origins.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() == blink::mojom::PermissionStatus::GRANTED) + return true; + } + return false; +} + +std::u16string FileSystemAccessPermissionContextQt::GetPickerTitle(const blink::mojom::FilePickerOptionsPtr &) +{ + return {}; +} + +void FileSystemAccessPermissionContextQt::PermissionGrantDestroyed( + FileSystemAccessPermissionGrantQt *grant) +{ + auto it = m_origins.find(grant->origin()); + if (it == m_origins.end()) + return; + + auto &grants = + grant->type() == GrantType::kRead ? it->second.read_grants : it->second.write_grants; + auto grant_it = grants.find(grant->path()); + + if (grant_it == grants.end()) { + return; + } + if (grant_it->second == grant) + grants.erase(grant_it); +} + +void FileSystemAccessPermissionContextQt::NotifyEntryMoved(const url::Origin &, const base::FilePath &, const base::FilePath &) +{ +} + +} // namespace QtWebEngineCore diff --git a/src/core/file_system_access/file_system_access_permission_context_qt.h b/src/core/file_system_access/file_system_access_permission_context_qt.h new file mode 100644 index 000000000..b569c81c0 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_qt.h @@ -0,0 +1,88 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// This file is based on chrome/browser/file_system_access/chrome_file_system_access_permission_context.h: +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_QT_H +#define FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_QT_H + +#include "base/files/file_path.h" +#include "components/keyed_service/core/keyed_service.h" +#include "content/public/browser/file_system_access_permission_context.h" +#include "content/public/browser/global_routing_id.h" + +namespace content { +class BrowserContext; +} + +namespace QtWebEngineCore { +class FileSystemAccessPermissionGrantQt; +class FileSystemAccessPermissionContextQt : public content::FileSystemAccessPermissionContext, + public KeyedService +{ +public: + explicit FileSystemAccessPermissionContextQt(content::BrowserContext *context); + ~FileSystemAccessPermissionContextQt() override; + + enum class GrantType { kRead, kWrite }; + + // 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; + FileSystemAccessPermissionContextQt::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 &) override; + void NotifyEntryMoved(const url::Origin &, const base::FilePath &, const base::FilePath &) override; + void OnFileCreatedFromShowSaveFilePicker(const GURL &file_picker_binding_context, + const storage::FileSystemURL &url) override{}; + + void NavigatedAwayFromOrigin(const url::Origin &origin); + content::BrowserContext *profile() const { return m_profile; } + + void PermissionGrantDestroyed(FileSystemAccessPermissionGrantQt *); + +private: + class PermissionGrantImpl; + + void DidConfirmSensitiveDirectoryAccess( + 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); + bool AncestorHasActivePermission(const url::Origin &origin, + const base::FilePath &path, + GrantType grant_type) const; + + content::BrowserContext *m_profile; + + // Permission state per origin. + struct OriginState; + std::map<url::Origin, OriginState> m_origins; + + std::map<std::string, FileSystemAccessPermissionContextQt::PathInfo> m_lastPickedDirectories; + + base::WeakPtrFactory<FileSystemAccessPermissionContextQt> m_weakFactory { this }; +}; + +} // namespace QtWebEngineCore + +#endif // FILE_SYSTEM_ACCESS_PERMISSION_CONTEXT_QT_H diff --git a/src/core/file_system_access/file_system_access_permission_grant_qt.cpp b/src/core/file_system_access/file_system_access_permission_grant_qt.cpp new file mode 100644 index 000000000..67fa1c8cf --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_grant_qt.cpp @@ -0,0 +1,146 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "file_system_access_permission_grant_qt.h" + +#include "file_system_access_permission_request_manager_qt.h" + +#include "components/permissions/permission_util.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.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 "content/public/browser/disallow_activation_reason.h" +#include "url/origin.h" + +namespace QtWebEngineCore { + +FileSystemAccessPermissionGrantQt::FileSystemAccessPermissionGrantQt( + base::WeakPtr<FileSystemAccessPermissionContextQt> context, const url::Origin &origin, + const base::FilePath &path, HandleType handle_type, GrantType type) + : m_context(context), m_origin(origin), m_path(path), m_handleType(handle_type), m_type(type) +{ +} +FileSystemAccessPermissionGrantQt::~FileSystemAccessPermissionGrantQt() +{ + if (m_context) + m_context->PermissionGrantDestroyed(this); +} +void FileSystemAccessPermissionGrantQt::RequestPermission( + content::GlobalRenderFrameHostId frame_id, UserActivationState user_activation_state, + base::OnceCallback<void(PermissionRequestOutcome)> callback) +{ + // 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() != blink::mojom::PermissionStatus::ASK || !m_context) { + if (GetStatus() == blink::mojom::PermissionStatus::GRANTED) + SetStatus(blink::mojom::PermissionStatus::GRANTED); + std::move(callback).Run(PermissionRequestOutcome::kRequestAborted); + return; + } + + // Otherwise, perform checks and ask the user for permission. + + content::RenderFrameHost *rfh = content::RenderFrameHost::FromID(frame_id); + if (!rfh) { + // Requested from a no longer valid render frame host. + std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame); + return; + } + + // Don't show request permission UI for an inactive RenderFrameHost as the + // page might not distinguish properly between user denying the permission + // and automatic rejection, leading to an inconsistent UX once the page + // becomes active again. + // - If this is called when RenderFrameHost is in BackForwardCache, evict + // the document from the cache. + // - If this is called when RenderFrameHost is in prerendering, cancel + // prerendering. + if (rfh->IsInactiveAndDisallowActivation( + content::DisallowActivationReasonId::kFileSystemAccessPermissionRequest)) { + 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; + } + + content::WebContents *web_contents = content::WebContents::FromRenderFrameHost(rfh); + if (!web_contents) { + // Requested from a worker, or a no longer existing tab. + std::move(callback).Run(PermissionRequestOutcome::kInvalidFrame); + return; + } + + url::Origin embedding_origin = url::Origin::Create(web_contents->GetLastCommittedURL()); + if (embedding_origin != m_origin) { + // Third party iframes are not allowed to request more permissions. + std::move(callback).Run(PermissionRequestOutcome::kThirdPartyContext); + return; + } + + auto *request_manager = + FileSystemAccessPermissionRequestManagerQt::FromWebContents(web_contents); + if (!request_manager) { + std::move(callback).Run(PermissionRequestOutcome::kRequestAborted); + return; + } + + // Drop fullscreen mode so that the user sees the URL bar. + base::ScopedClosureRunner fullscreen_block = web_contents->ForSecurityDropFullscreen(); + + FileSystemAccessPermissionRequestManagerQt::Access access = m_type == GrantType::kRead + ? FileSystemAccessPermissionRequestManagerQt::Access::kRead + : FileSystemAccessPermissionRequestManagerQt::Access::kWrite; + + // If a website wants both read and write access, code in content will + // request those as two separate requests. The |request_manager| will then + // detect this and combine the two requests into one prompt. As such this + // code does not have to have any way to request Access::kReadWrite. + + request_manager->AddRequest( + { m_origin, m_path, m_handleType, access }, + base::BindOnce(&FileSystemAccessPermissionGrantQt::OnPermissionRequestResult, this, + std::move(callback)), + std::move(fullscreen_block)); +} + +void FileSystemAccessPermissionGrantQt::SetStatus(blink::mojom::PermissionStatus status) +{ + bool should_notify = m_status != status; + m_status = status; + if (should_notify) + NotifyPermissionStatusChanged(); +} + +void FileSystemAccessPermissionGrantQt::OnPermissionRequestResult( + base::OnceCallback<void(PermissionRequestOutcome)> callback, permissions::PermissionAction result) +{ + switch (result) { + case permissions::PermissionAction::GRANTED: + SetStatus(blink::mojom::PermissionStatus::GRANTED); + std::move(callback).Run(PermissionRequestOutcome::kUserGranted); + break; + case permissions::PermissionAction::DENIED: + SetStatus(blink::mojom::PermissionStatus::DENIED); + std::move(callback).Run(PermissionRequestOutcome::kUserDenied); + break; + case permissions::PermissionAction::DISMISSED: + case permissions::PermissionAction::IGNORED: + std::move(callback).Run(PermissionRequestOutcome::kUserDismissed); + break; + case permissions::PermissionAction::REVOKED: + case permissions::PermissionAction::GRANTED_ONCE: + case permissions::PermissionAction::NUM: + NOTREACHED(); + break; + } +} + +} // namespace QtWebEngineCore diff --git a/src/core/file_system_access/file_system_access_permission_grant_qt.h b/src/core/file_system_access/file_system_access_permission_grant_qt.h new file mode 100644 index 000000000..829d2b889 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_grant_qt.h @@ -0,0 +1,59 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef FILE_SYSTEM_ACCESS_PERMISSION_GRANT_QT_H +#define FILE_SYSTEM_ACCESS_PERMISSION_GRANT_QT_H + +#include "content/public/browser/file_system_access_permission_grant.h" +#include "url/origin.h" +#include "file_system_access_permission_context_qt.h" +#include "content/public/browser/global_routing_id.h" +#include "components/permissions/permission_util.h" + +namespace QtWebEngineCore { + +using HandleType = content::FileSystemAccessPermissionContext::HandleType; +using GrantType = FileSystemAccessPermissionContextQt::GrantType; + +class FileSystemAccessPermissionGrantQt : public content::FileSystemAccessPermissionGrant +{ +public: + FileSystemAccessPermissionGrantQt(base::WeakPtr<FileSystemAccessPermissionContextQt> context, + const url::Origin &origin, const base::FilePath &path, + HandleType handle_type, GrantType type); + + // content::FileSystemAccessPermissionGrant: + blink::mojom::PermissionStatus GetStatus() override { return m_status; } + base::FilePath GetPath() override { return m_path; } + void RequestPermission(content::GlobalRenderFrameHostId frame_id, + UserActivationState user_activation_state, + base::OnceCallback<void(PermissionRequestOutcome)> callback) override; + + const url::Origin &origin() const { return m_origin; } + HandleType handleType() const { return m_handleType; } + const base::FilePath &path() const { return m_path; } + GrantType type() const { return m_type; } + + void SetStatus(blink::mojom::PermissionStatus status); + +protected: + ~FileSystemAccessPermissionGrantQt() override; + +private: + void OnPermissionRequestResult(base::OnceCallback<void(PermissionRequestOutcome)> callback, + permissions::PermissionAction result); + + base::WeakPtr<FileSystemAccessPermissionContextQt> const m_context; + const url::Origin m_origin; + const base::FilePath m_path; + const HandleType m_handleType; + const GrantType m_type; + + // This member should only be updated via SetStatus(), to make sure + // observers are properly notified about any change in status. + blink::mojom::PermissionStatus m_status = blink::mojom::PermissionStatus::ASK; +}; + +} // namespace QtWebEngineCore + +#endif // FILE_SYSTEM_ACCESS_PERMISSION_GRANT_QT_H diff --git a/src/core/file_system_access/file_system_access_permission_request_controller.h b/src/core/file_system_access/file_system_access_permission_request_controller.h new file mode 100644 index 000000000..e659f81a7 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_controller.h @@ -0,0 +1,39 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_CONTROLLER_H +#define FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_CONTROLLER_H + +#include "api/qwebenginefilesystemaccessrequest.h" +#include "request_controller.h" + +using HandleType = QWebEngineFileSystemAccessRequest::HandleType; +using AccessFlags = QWebEngineFileSystemAccessRequest::AccessFlags; + +namespace QtWebEngineCore { + +class FileSystemAccessPermissionRequestController : public RequestController +{ +public: + FileSystemAccessPermissionRequestController(const QUrl &origin, const QUrl &filePath, + HandleType handleType, AccessFlags accessType) + : RequestController(origin) + , m_filePath(filePath) + , m_handleType(handleType) + , m_accessType(accessType) + { + } + + QUrl filePath() const { return m_filePath; } + HandleType handleType() const { return m_handleType; } + AccessFlags accessFlags() const { return m_accessType; } + +private: + QUrl m_filePath; + HandleType m_handleType; + AccessFlags m_accessType; +}; + +} // namespace QtWebEngineCore + +#endif // FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_CONTROLLER_H diff --git a/src/core/file_system_access/file_system_access_permission_request_controller_impl.cpp b/src/core/file_system_access/file_system_access_permission_request_controller_impl.cpp new file mode 100644 index 000000000..f77c974d0 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_controller_impl.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "file_system_access_permission_request_controller_impl.h" + +#include "components/permissions/permission_util.h" +#include "content/public/browser/file_system_access_permission_context.h" +#include "type_conversion.h" + +namespace QtWebEngineCore { + +ASSERT_ENUMS_MATCH(content::FileSystemAccessPermissionContext::HandleType::kFile, + QWebEngineFileSystemAccessRequest::HandleType::File); +ASSERT_ENUMS_MATCH(content::FileSystemAccessPermissionContext::HandleType::kDirectory, + QWebEngineFileSystemAccessRequest::HandleType::Directory); + +ASSERT_ENUMS_MATCH(FileSystemAccessPermissionRequestManagerQt::Access::kRead, + QWebEngineFileSystemAccessRequest::AccessFlag::Read); +ASSERT_ENUMS_MATCH(FileSystemAccessPermissionRequestManagerQt::Access::kWrite, + QWebEngineFileSystemAccessRequest::AccessFlag::Write); + +FileSystemAccessPermissionRequestControllerImpl::FileSystemAccessPermissionRequestControllerImpl( + const FileSystemAccessPermissionRequestManagerQt::RequestData &request, + base::OnceCallback<void(permissions::PermissionAction result)> callback) + : FileSystemAccessPermissionRequestController( + toQt(request.origin.GetURL()), QUrl::fromLocalFile(toQt(request.path.value())), + (HandleType)request.handle_type, AccessFlags((int)request.access)) + , m_callback(std::move(callback)) +{ +} + +FileSystemAccessPermissionRequestControllerImpl::~FileSystemAccessPermissionRequestControllerImpl() +{ + if (m_callback) + std::move(m_callback).Run(permissions::PermissionAction::IGNORED); +} + +void FileSystemAccessPermissionRequestControllerImpl::accepted() +{ + std::move(m_callback).Run(permissions::PermissionAction::GRANTED); +} + +void FileSystemAccessPermissionRequestControllerImpl::rejected() +{ + std::move(m_callback).Run(permissions::PermissionAction::DENIED); +} + +} // namespace QtWebEngineCore diff --git a/src/core/file_system_access/file_system_access_permission_request_controller_impl.h b/src/core/file_system_access/file_system_access_permission_request_controller_impl.h new file mode 100644 index 000000000..6cca1e549 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_controller_impl.h @@ -0,0 +1,32 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_CONTROLLER_IMPL_H +#define FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_CONTROLLER_IMPL_H + +#include "file_system_access_permission_request_controller.h" +#include "file_system_access_permission_request_manager_qt.h" + +namespace QtWebEngineCore { + +class FileSystemAccessPermissionRequestControllerImpl final + : public FileSystemAccessPermissionRequestController +{ +public: + FileSystemAccessPermissionRequestControllerImpl( + const FileSystemAccessPermissionRequestManagerQt::RequestData &request, + base::OnceCallback<void(permissions::PermissionAction result)> callback); + + ~FileSystemAccessPermissionRequestControllerImpl(); + +protected: + void accepted() override; + void rejected() override; + +private: + base::OnceCallback<void(permissions::PermissionAction result)> m_callback; +}; + +} // namespace QtWebEngineCore + +#endif // FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_CONTROLLER_IMPL_H diff --git a/src/core/file_system_access/file_system_access_permission_request_manager_qt.cpp b/src/core/file_system_access/file_system_access_permission_request_manager_qt.cpp new file mode 100644 index 000000000..c384dc7b3 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_manager_qt.cpp @@ -0,0 +1,195 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "file_system_access_permission_request_manager_qt.h" + +#include "components/permissions/permission_util.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/navigation_handle.h" + +#include "api/qwebenginefilesystemaccessrequest.h" +#include "file_system_access_permission_context_factory_qt.h" +#include "file_system_access_permission_request_controller_impl.h" +#include "web_contents_adapter_client.h" +#include "web_contents_view_qt.h" + +namespace QtWebEngineCore { + +bool RequestsAreIdentical(const FileSystemAccessPermissionRequestManagerQt::RequestData &a, + const FileSystemAccessPermissionRequestManagerQt::RequestData &b) +{ + return a.origin == b.origin && a.path == b.path && a.handle_type == b.handle_type + && a.access == b.access; +} + +bool RequestsAreForSamePath(const FileSystemAccessPermissionRequestManagerQt::RequestData &a, + const FileSystemAccessPermissionRequestManagerQt::RequestData &b) +{ + return a.origin == b.origin && a.path == b.path && a.handle_type == b.handle_type; +} + +struct FileSystemAccessPermissionRequestManagerQt::Request +{ + Request(RequestData data, + base::OnceCallback<void(permissions::PermissionAction result)> callback, + base::ScopedClosureRunner fullscreen_block) + : data(std::move(data)) + { + callbacks.push_back(std::move(callback)); + fullscreen_blocks.push_back(std::move(fullscreen_block)); + } + + RequestData data; + std::vector<base::OnceCallback<void(permissions::PermissionAction result)>> callbacks; + std::vector<base::ScopedClosureRunner> fullscreen_blocks; +}; + +FileSystemAccessPermissionRequestManagerQt::~FileSystemAccessPermissionRequestManagerQt() = default; + +void FileSystemAccessPermissionRequestManagerQt::AddRequest( + RequestData data, base::OnceCallback<void(permissions::PermissionAction result)> callback, + base::ScopedClosureRunner fullscreen_block) +{ + // Check if any pending requests are identical to the new request. + if (m_currentRequest && RequestsAreIdentical(m_currentRequest->data, data)) { + m_currentRequest->callbacks.push_back(std::move(callback)); + m_currentRequest->fullscreen_blocks.push_back(std::move(fullscreen_block)); + return; + } + for (const auto &request : m_queuedRequests) { + if (RequestsAreIdentical(request->data, data)) { + request->callbacks.push_back(std::move(callback)); + request->fullscreen_blocks.push_back(std::move(fullscreen_block)); + return; + } + if (RequestsAreForSamePath(request->data, data)) { + // This means access levels are different. Change the existing request + // to kReadWrite, and add the new callback. + request->data.access = Access::kReadWrite; + request->callbacks.push_back(std::move(callback)); + request->fullscreen_blocks.push_back(std::move(fullscreen_block)); + return; + } + } + + m_queuedRequests.push_back(std::make_unique<Request>(std::move(data), std::move(callback), + std::move(fullscreen_block))); + if (!IsShowingRequest()) + ScheduleShowRequest(); +} + +FileSystemAccessPermissionRequestManagerQt::FileSystemAccessPermissionRequestManagerQt( + content::WebContents *web_contents) + : content::WebContentsObserver(web_contents) + , content::WebContentsUserData<FileSystemAccessPermissionRequestManagerQt>(*web_contents) +{ +} + +bool FileSystemAccessPermissionRequestManagerQt::CanShowRequest() const +{ + // Delay showing requests until the main frame is fully loaded. + // ScheduleShowRequest() will be called again when that happens. + return web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame() && !m_queuedRequests.empty() + && !m_currentRequest; +} + +void FileSystemAccessPermissionRequestManagerQt::ScheduleShowRequest() +{ + if (!CanShowRequest()) + return; + + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&FileSystemAccessPermissionRequestManagerQt::DequeueAndShowRequest, + m_weakFactory.GetWeakPtr())); +} + +void FileSystemAccessPermissionRequestManagerQt::DequeueAndShowRequest() +{ + if (!CanShowRequest()) + return; + + m_currentRequest = std::move(m_queuedRequests.front()); + m_queuedRequests.pop_front(); + + WebContentsAdapterClient *client = + WebContentsViewQt::from( + static_cast<content::WebContentsImpl *>(web_contents())->GetView()) + ->client(); + if (!client) { + LOG(ERROR) + << "Attempt to request file system access from content missing WebContents client"; + for (auto &callback : m_currentRequest->callbacks) + std::move(callback).Run(permissions::PermissionAction::DENIED); + return; + } + + QWebEngineFileSystemAccessRequest request( + std::make_shared<FileSystemAccessPermissionRequestControllerImpl>( + m_currentRequest->data, + base::BindOnce( + &FileSystemAccessPermissionRequestManagerQt::OnPermissionDialogResult, + m_weakFactory.GetWeakPtr()))); + client->runFileSystemAccessRequest(std::move(request)); +} + +void FileSystemAccessPermissionRequestManagerQt::DocumentOnLoadCompletedInPrimaryMainFrame() +{ + // This is scheduled because while all calls to the browser have been + // issued at DOMContentLoaded, they may be bouncing around in scheduled + // callbacks finding the UI thread still. This makes sure we allow those + // scheduled calls to AddRequest to complete before we show the page-load + // permissions bubble. + if (!m_queuedRequests.empty()) + ScheduleShowRequest(); +} + +void FileSystemAccessPermissionRequestManagerQt::DidFinishNavigation( + content::NavigationHandle *navigation) +{ + // We only care about top-level navigations that actually committed. + if (!navigation->IsInMainFrame() || !navigation->HasCommitted()) + return; + + auto src_origin = url::Origin::Create(navigation->GetPreviousPrimaryMainFrameURL()); + auto dest_origin = url::Origin::Create(navigation->GetURL()); + if (src_origin == dest_origin) + return; + + // Navigated away from |src_origin|, tell permission context to check if + // permissions need to be revoked. + auto *context = FileSystemAccessPermissionContextFactoryQt::GetForProfileIfExists( + web_contents()->GetBrowserContext()); + if (context) + context->NavigatedAwayFromOrigin(src_origin); +} + +void FileSystemAccessPermissionRequestManagerQt::WebContentsDestroyed() +{ + auto src_origin = web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(); + + // Navigated away from |src_origin|, tell permission context to check if + // permissions need to be revoked. + auto *context = FileSystemAccessPermissionContextFactoryQt::GetForProfileIfExists( + web_contents()->GetBrowserContext()); + if (context) + context->NavigatedAwayFromOrigin(src_origin); +} + +void FileSystemAccessPermissionRequestManagerQt::OnPermissionDialogResult( + permissions::PermissionAction result) +{ + DCHECK(m_currentRequest); + for (auto &callback : m_currentRequest->callbacks) + std::move(callback).Run(result); + + m_currentRequest = nullptr; + if (!m_queuedRequests.empty()) + ScheduleShowRequest(); +} + +WEB_CONTENTS_USER_DATA_KEY_IMPL(FileSystemAccessPermissionRequestManagerQt); + +} // namespace QtWebEngineCore diff --git a/src/core/file_system_access/file_system_access_permission_request_manager_qt.h b/src/core/file_system_access/file_system_access_permission_request_manager_qt.h new file mode 100644 index 000000000..840854911 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_manager_qt.h @@ -0,0 +1,79 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_MANAGER_QT_H +#define FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_MANAGER_QT_H + +#include "base/containers/circular_deque.h" +#include "base/files/file_path.h" +#include "base/functional/callback_helpers.h" +#include "base/memory/weak_ptr.h" +#include "content/public/browser/file_system_access_permission_context.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" +#include "url/origin.h" + +namespace permissions { +enum class PermissionAction; +} + +namespace QtWebEngineCore { + +class FileSystemAccessPermissionRequestManagerQt + : public content::WebContentsObserver, + public content::WebContentsUserData<FileSystemAccessPermissionRequestManagerQt> +{ +public: + ~FileSystemAccessPermissionRequestManagerQt() override; + + enum class Access { + // Only ask for read access. + kRead = 0x1, + // Only ask for write access, assuming read access has already been granted. + kWrite = 0x2, + // Ask for both read and write access. + kReadWrite = 0x3 + }; + + struct RequestData + { + url::Origin origin; + base::FilePath path; + content::FileSystemAccessPermissionContext::HandleType handle_type; + Access access; + }; + + void AddRequest(RequestData request, + base::OnceCallback<void(permissions::PermissionAction result)> callback, + base::ScopedClosureRunner fullscreen_block); + +private: + friend class content::WebContentsUserData<FileSystemAccessPermissionRequestManagerQt>; + + explicit FileSystemAccessPermissionRequestManagerQt(content::WebContents *web_contents); + + bool IsShowingRequest() const { return m_currentRequest != nullptr; } + bool CanShowRequest() const; + void ScheduleShowRequest(); + void DequeueAndShowRequest(); + + // content::WebContentsObserver + void DocumentOnLoadCompletedInPrimaryMainFrame() override; + void DidFinishNavigation(content::NavigationHandle *navigation_handle) override; + void WebContentsDestroyed() override; + + void OnPermissionDialogResult(permissions::PermissionAction result); + + struct Request; + // Request currently being shown in prompt. + std::unique_ptr<Request> m_currentRequest; + // Queued up requests. + base::circular_deque<std::unique_ptr<Request>> m_queuedRequests; + + base::WeakPtrFactory<FileSystemAccessPermissionRequestManagerQt> m_weakFactory { this }; + WEB_CONTENTS_USER_DATA_KEY_DECL(); +}; + +} // namespace QtWebEngineCore + +#endif // FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_MANAGER_QT_H |