diff options
author | Szabolcs David <davidsz@inf.u-szeged.hu> | 2022-05-09 13:06:06 +0200 |
---|---|---|
committer | Szabolcs David <davidsz@inf.u-szeged.hu> | 2022-05-26 12:10:35 +0200 |
commit | 567739fda232c28992962f32a9e652eab723a4d4 (patch) | |
tree | cb933c189d87ed104f71ad8393d9e93d0eec193a | |
parent | 2ba1f04b4589e5883a399b022b7795266c4d4646 (diff) |
Implement File System Access permission API
Allow web pages to safely access the local file system
by exposing a permission API. Permissions are stored in-memory.
The built-in access rules are the same as the behavior of Chrome:
JS can't request access to system libraries, sensitive directories
and the application itself.
Task-number: QTBUG-97829
Change-Id: Ic675422cafbad5a90243b4fa8f0749c46afa192c
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
29 files changed, 1929 insertions, 6 deletions
diff --git a/examples/webenginewidgets/simplebrowser/webview.cpp b/examples/webenginewidgets/simplebrowser/webview.cpp index 31be9f34c..661dd5955 100644 --- a/examples/webenginewidgets/simplebrowser/webview.cpp +++ b/examples/webenginewidgets/simplebrowser/webview.cpp @@ -144,6 +144,8 @@ void WebView::setPage(WebPage *page) &WebView::handleProxyAuthenticationRequired); disconnect(oldPage, &QWebEnginePage::registerProtocolHandlerRequested, this, &WebView::handleRegisterProtocolHandlerRequested); + disconnect(oldPage, &QWebEnginePage::fileSystemAccessRequested, this, + &WebView::handleFileSystemAccessRequested); } createWebActionTrigger(page,QWebEnginePage::Forward); createWebActionTrigger(page,QWebEnginePage::Back); @@ -159,6 +161,8 @@ void WebView::setPage(WebPage *page) &WebView::handleProxyAuthenticationRequired); connect(page, &QWebEnginePage::registerProtocolHandlerRequested, this, &WebView::handleRegisterProtocolHandlerRequested); + connect(page, &QWebEnginePage::fileSystemAccessRequested, this, + &WebView::handleFileSystemAccessRequested); } int WebView::loadProgress() const @@ -347,3 +351,31 @@ void WebView::handleRegisterProtocolHandlerRequested( request.reject(); } //! [registerProtocolHandlerRequested] + +void WebView::handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request) +{ + QString accessType; + switch (request.accessFlags()) { + case QWebEngineFileSystemAccessRequest::Read: + accessType = "read"; + break; + case QWebEngineFileSystemAccessRequest::Write: + accessType = "write"; + break; + case QWebEngineFileSystemAccessRequest::Read | QWebEngineFileSystemAccessRequest::Write: + accessType = "read and write"; + break; + default: + Q_UNREACHABLE(); + } + + auto answer = QMessageBox::question(window(), tr("File system access reques"), + tr("Give %1 %2 access to %3?") + .arg(request.origin().host()) + .arg(accessType) + .arg(request.filePath().toString())); + if (answer == QMessageBox::Yes) + request.accept(); + else + request.reject(); +} diff --git a/examples/webenginewidgets/simplebrowser/webview.h b/examples/webenginewidgets/simplebrowser/webview.h index 0dc7c33ad..7d8ff4e59 100644 --- a/examples/webenginewidgets/simplebrowser/webview.h +++ b/examples/webenginewidgets/simplebrowser/webview.h @@ -54,6 +54,7 @@ #include <QIcon> #include <QWebEngineView> #include <QWebEngineCertificateError> +#include <QWebEngineFileSystemAccessRequest> #include <QWebEnginePage> #include <QWebEngineRegisterProtocolHandlerRequest> @@ -87,6 +88,7 @@ private slots: void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost); void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); + void handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request); private: void createWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 83f914856..94ebe0b9b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -110,6 +110,12 @@ foreach(arch ${archs}) favicon_driver_qt.cpp favicon_driver_qt.h favicon_service_factory_qt.cpp favicon_service_factory_qt.h file_picker_controller.cpp file_picker_controller.h + file_system_access/file_system_access_permission_context_factory_qt.cpp file_system_access/file_system_access_permission_context_factory_qt.h + file_system_access/file_system_access_permission_context_qt.cpp file_system_access/file_system_access_permission_context_qt.h + file_system_access/file_system_access_permission_grant_qt.cpp file_system_access/file_system_access_permission_grant_qt.h + file_system_access/file_system_access_permission_request_controller.h + file_system_access/file_system_access_permission_request_controller_impl.cpp file_system_access/file_system_access_permission_request_controller_impl.h + file_system_access/file_system_access_permission_request_manager_qt.cpp file_system_access/file_system_access_permission_request_manager_qt.h find_text_helper.cpp find_text_helper.h global_descriptors_qt.h javascript_dialog_controller.cpp javascript_dialog_controller.h javascript_dialog_controller_p.h diff --git a/src/core/api/CMakeLists.txt b/src/core/api/CMakeLists.txt index e46c95be3..2d493c04c 100644 --- a/src/core/api/CMakeLists.txt +++ b/src/core/api/CMakeLists.txt @@ -12,6 +12,7 @@ qt_internal_add_module(WebEngineCore qwebenginecontextmenurequest.cpp qwebenginecontextmenurequest.h qwebenginecontextmenurequest_p.h qwebenginecookiestore.cpp qwebenginecookiestore.h qwebenginecookiestore_p.h qwebenginedownloadrequest.cpp qwebenginedownloadrequest.h qwebenginedownloadrequest_p.h + qwebenginefilesystemaccessrequest.cpp qwebenginefilesystemaccessrequest.h qwebenginefindtextresult.cpp qwebenginefindtextresult.h qwebenginefullscreenrequest.cpp qwebenginefullscreenrequest.h qwebenginehistory.cpp qwebenginehistory.h qwebenginehistory_p.h diff --git a/src/core/api/qwebenginefilesystemaccessrequest.cpp b/src/core/api/qwebenginefilesystemaccessrequest.cpp new file mode 100644 index 000000000..7602d69bb --- /dev/null +++ b/src/core/api/qwebenginefilesystemaccessrequest.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwebenginefilesystemaccessrequest.h" + +#include "file_system_access/file_system_access_permission_request_controller.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QWebEngineFileSystemAccessRequest + \brief The QWebEngineFileSystemAccessRequest class enables accepting or rejecting + requests for local file system access from JavaScript applications. + + \since 6.4 + + \inmodule QtWebEngineCore + + To allow web applications to access local files of the computer, + applications must connect to QWebEnginePage::fileSystemAccessRequested, which takes a + QWebEngineFileSystemAccessRequest instance as an argument. + + If a web applications requests access to local files or directories, + QWebEnginePage::fileSystemAccessRequested will be emitted with an + QWebEngineFileSystemAccessRequest instance as an argument where accessFlags() indicates + the type of the requested access: read, write or both. The signal handler needs to then + either call accept() or reject(). +*/ + +QWebEngineFileSystemAccessRequest::QWebEngineFileSystemAccessRequest( + const QWebEngineFileSystemAccessRequest &other) = default; +QWebEngineFileSystemAccessRequest &QWebEngineFileSystemAccessRequest::operator=( + const QWebEngineFileSystemAccessRequest &other) = default; +QWebEngineFileSystemAccessRequest::QWebEngineFileSystemAccessRequest( + QWebEngineFileSystemAccessRequest &&other) = default; +QWebEngineFileSystemAccessRequest & +QWebEngineFileSystemAccessRequest::operator=(QWebEngineFileSystemAccessRequest &&other) = default; +QWebEngineFileSystemAccessRequest::~QWebEngineFileSystemAccessRequest() = default; + +/*! \fn bool QWebEngineFileSystemAccessRequest::operator==(const QWebEngineFileSystemAccessRequest &that) const + Returns \c true if \a that points to the same object as this request. +*/ +bool QWebEngineFileSystemAccessRequest::operator==( + const QWebEngineFileSystemAccessRequest &that) const +{ + return d_ptr == that.d_ptr; +} + +/*! \fn bool QWebEngineFileSystemAccessRequest::operator!=(const QWebEngineFileSystemAccessRequest &that) const + Returns \c true if \a that points to a different object than this request. +*/ +bool QWebEngineFileSystemAccessRequest::operator!=( + const QWebEngineFileSystemAccessRequest &that) const +{ + return d_ptr != that.d_ptr; +} + +/*! \internal */ +QWebEngineFileSystemAccessRequest::QWebEngineFileSystemAccessRequest( + QSharedPointer<QtWebEngineCore::FileSystemAccessPermissionRequestController> controller) + : d_ptr(controller) +{ +} + +/*! + Rejects a request to access local files. +*/ +void QWebEngineFileSystemAccessRequest::reject() +{ + d_ptr->reject(); +} + +/*! + Accepts the request to access local files. +*/ +void QWebEngineFileSystemAccessRequest::accept() +{ + d_ptr->accept(); +} + +/*! + \property QWebEngineFileSystemAccessRequest::origin + \brief The URL of the web page that issued the file system access request. +*/ + +QUrl QWebEngineFileSystemAccessRequest::origin() const +{ + return d_ptr->origin(); +} + +/*! + \property QWebEngineFileSystemAccessRequest::filePath + \brief Returns the file path this file system access request is referring to. +*/ + +QUrl QWebEngineFileSystemAccessRequest::filePath() const +{ + return d_ptr->filePath(); +} + +/*! + \property QWebEngineFileSystemAccessRequest::handleType + \brief Returns the type of the requested file system entry. (File or directory) + */ +HandleType QWebEngineFileSystemAccessRequest::handleType() const +{ + return d_ptr->handleType(); +} + +/*! + \property QWebEngineFileSystemAccessRequest::accessFlags + \brief Contains the requested file access rights. + */ +AccessFlags QWebEngineFileSystemAccessRequest::accessFlags() const +{ + return d_ptr->accessFlags(); +} + +QT_END_NAMESPACE diff --git a/src/core/api/qwebenginefilesystemaccessrequest.h b/src/core/api/qwebenginefilesystemaccessrequest.h new file mode 100644 index 000000000..84bcf0f90 --- /dev/null +++ b/src/core/api/qwebenginefilesystemaccessrequest.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWEBENGINEFILESYSTEMACCESSREQUEST_H +#define QWEBENGINEFILESYSTEMACCESSREQUEST_H + +#include <QtWebEngineCore/qtwebenginecoreglobal.h> + +#include <QtCore/qsharedpointer.h> +#include <QtCore/qurl.h> + +namespace QtWebEngineCore { +class FileSystemAccessPermissionRequestController; +class FileSystemAccessPermissionRequestManagerQt; +} + +QT_BEGIN_NAMESPACE + +class Q_WEBENGINECORE_EXPORT QWebEngineFileSystemAccessRequest +{ + Q_GADGET + Q_PROPERTY(QUrl origin READ origin CONSTANT FINAL) + Q_PROPERTY(QUrl filePath READ filePath CONSTANT FINAL) + Q_PROPERTY(HandleType handleType READ handleType CONSTANT FINAL) + Q_PROPERTY(AccessFlags accessFlags READ accessFlags CONSTANT FINAL) + +public: + QWebEngineFileSystemAccessRequest(const QWebEngineFileSystemAccessRequest &other); + QWebEngineFileSystemAccessRequest &operator=(const QWebEngineFileSystemAccessRequest &other); + QWebEngineFileSystemAccessRequest(QWebEngineFileSystemAccessRequest &&other); + QWebEngineFileSystemAccessRequest &operator=(QWebEngineFileSystemAccessRequest &&other); + ~QWebEngineFileSystemAccessRequest(); + + enum HandleType { File, Directory }; + Q_ENUM(HandleType) + + enum AccessFlag { Read = 0x1, Write = 0x2 }; + Q_DECLARE_FLAGS(AccessFlags, AccessFlag) + Q_FLAG(AccessFlags) + + Q_INVOKABLE void accept(); + Q_INVOKABLE void reject(); + QUrl origin() const; + QUrl filePath() const; + HandleType handleType() const; + AccessFlags accessFlags() const; + + bool operator==(const QWebEngineFileSystemAccessRequest &that) const; + bool operator!=(const QWebEngineFileSystemAccessRequest &that) const; + +private: + QWebEngineFileSystemAccessRequest( + QSharedPointer<QtWebEngineCore::FileSystemAccessPermissionRequestController>); + friend QtWebEngineCore::FileSystemAccessPermissionRequestManagerQt; + + QSharedPointer<QtWebEngineCore::FileSystemAccessPermissionRequestController> d_ptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QWebEngineFileSystemAccessRequest::AccessFlags) + +QT_END_NAMESPACE + +#endif // QWEBENGINEFILESYSTEMACCESSREQUEST_H diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp index 57d66f7fa..0b3cba785 100644 --- a/src/core/api/qwebenginepage.cpp +++ b/src/core/api/qwebenginepage.cpp @@ -41,6 +41,7 @@ #include "qwebenginepage_p.h" #include "qwebenginecertificateerror.h" +#include "qwebenginefilesystemaccessrequest.h" #include "qwebenginefindtextresult.h" #include "qwebenginefullscreenrequest.h" #include "qwebenginehistory.h" @@ -180,6 +181,7 @@ QWebEnginePagePrivate::QWebEnginePagePrivate(QWebEngineProfile *_profile) qRegisterMetaType<QWebEngineQuotaRequest>(); qRegisterMetaType<QWebEngineRegisterProtocolHandlerRequest>(); + qRegisterMetaType<QWebEngineFileSystemAccessRequest>(); qRegisterMetaType<QWebEngineFindTextResult>(); // See setVisible(). @@ -578,6 +580,12 @@ void QWebEnginePagePrivate::runRegisterProtocolHandlerRequest(QWebEngineRegister Q_EMIT q->registerProtocolHandlerRequested(request); } +void QWebEnginePagePrivate::runFileSystemAccessRequest(QWebEngineFileSystemAccessRequest request) +{ + Q_Q(QWebEnginePage); + Q_EMIT q->fileSystemAccessRequested(request); +} + QObject *QWebEnginePagePrivate::accessibilityParentObject() { return view ? view->accessibilityParentObject() : nullptr; diff --git a/src/core/api/qwebenginepage.h b/src/core/api/qwebenginepage.h index 45829f603..68fee0f84 100644 --- a/src/core/api/qwebenginepage.h +++ b/src/core/api/qwebenginepage.h @@ -61,6 +61,7 @@ class QContextMenuBuilder; class QWebChannel; class QWebEngineCertificateError; class QWebEngineClientCertificateSelection; +class QWebEngineFileSystemAccessRequest; class QWebEngineFindTextResult; class QWebEngineFullScreenRequest; class QWebEngineHistory; @@ -345,6 +346,7 @@ Q_SIGNALS: void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); void quotaRequested(QWebEngineQuotaRequest quotaRequest); void registerProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request); + void fileSystemAccessRequested(QWebEngineFileSystemAccessRequest request); void selectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection); void authenticationRequired(const QUrl &requestUrl, QAuthenticator *authenticator); void proxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *authenticator, const QString &proxyHost); diff --git a/src/core/api/qwebenginepage_p.h b/src/core/api/qwebenginepage_p.h index 29b253755..785930127 100644 --- a/src/core/api/qwebenginepage_p.h +++ b/src/core/api/qwebenginepage_p.h @@ -169,6 +169,7 @@ public: void runMouseLockPermissionRequest(const QUrl &securityOrigin) override; void runQuotaRequest(QWebEngineQuotaRequest) override; void runRegisterProtocolHandlerRequest(QWebEngineRegisterProtocolHandlerRequest) override; + void runFileSystemAccessRequest(QWebEngineFileSystemAccessRequest) override; QObject *accessibilityParentObject() override; QWebEngineSettings *webEngineSettings() const override; void allowCertificateError(const QWebEngineCertificateError &error) override; diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp index bede258f3..01b4fa1f3 100644 --- a/src/core/content_browser_client_qt.cpp +++ b/src/core/content_browser_client_qt.cpp @@ -92,6 +92,7 @@ #include "client_cert_select_controller.h" #include "custom_handlers/protocol_handler_registry_factory.h" #include "devtools_manager_delegate_qt.h" +#include "file_system_access/file_system_access_permission_request_manager_qt.h" #include "login_delegate_qt.h" #include "media_capture_devices_dispatcher.h" #include "net/cookie_monster_delegate_qt.h" @@ -1250,6 +1251,7 @@ void ContentBrowserClientQt::SiteInstanceDeleting(content::SiteInstance *site_in content::WebContentsViewDelegate *ContentBrowserClientQt::GetWebContentsViewDelegate(content::WebContents *web_contents) { FormInteractionTabHelper::CreateForWebContents(web_contents); + FileSystemAccessPermissionRequestManagerQt::CreateForWebContents(web_contents); if (auto *registry = performance_manager::PerformanceManagerRegistry::GetInstance()) registry->MaybeCreatePageNodeForWebContents(web_contents); 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..3640cb1e2 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_factory_qt.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..3dfc5c64a --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_factory_qt.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..215fcf4a4 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_qt.cpp @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// 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); + + std::vector<base::FilePath::StringType> diff_components; + diff.GetComponents(&diff_components); + 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) +{ + Q_UNUSED(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(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(); + } + + return existing_grant; +} + +scoped_refptr<content::FileSystemAccessPermissionGrant> +FileSystemAccessPermissionContextQt::GetWritePermissionGrant(const url::Origin &origin, + const base::FilePath &path, + HandleType handle_type, + UserAction user_action) +{ + Q_UNUSED(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(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(); + } + + return existing_grant; +} + +void FileSystemAccessPermissionContextQt::ConfirmSensitiveDirectoryAccess( + const url::Origin &origin, PathType path_type, const base::FilePath &path, + HandleType handle_type, content::GlobalRenderFrameHostId frame_id, + base::OnceCallback<void(SensitiveDirectoryResult)> callback) +{ + if (path_type == PathType::kExternal) { + std::move(callback).Run(SensitiveDirectoryResult::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, 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) +{ + QStandardPaths::StandardLocation location = QStandardPaths::DocumentsLocation; + switch (directory) { + case blink::mojom::WellKnownDirectory::kDefault: + location = QStandardPaths::DocumentsLocation; + break; + 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(PermissionStatus::ASK); + for (auto &grant : origin_state.write_grants) + grant.second->SetStatus(PermissionStatus::ASK); +} + +void FileSystemAccessPermissionContextQt::DidConfirmSensitiveDirectoryAccess( + const url::Origin &origin, const base::FilePath &path, HandleType handle_type, + content::GlobalRenderFrameHostId frame_id, + base::OnceCallback<void(SensitiveDirectoryResult)> callback, bool should_block) +{ + Q_UNUSED(origin); + Q_UNUSED(path); + Q_UNUSED(handle_type); + Q_UNUSED(frame_id); + + if (should_block) + std::move(callback).Run(SensitiveDirectoryResult::kAbort); + else + std::move(callback).Run(SensitiveDirectoryResult::kAllowed); +} + +} // 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..36aea7376 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_context_qt.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// 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 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 ConfirmSensitiveDirectoryAccess( + const url::Origin &origin, PathType path_type, const base::FilePath &path, + HandleType handle_type, content::GlobalRenderFrameHostId frame_id, + base::OnceCallback<void(SensitiveDirectoryResult)> 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) override; + + void NavigatedAwayFromOrigin(const url::Origin &origin); + content::BrowserContext *profile() const { return m_profile; } + +private: + class PermissionGrantImpl; + + void DidConfirmSensitiveDirectoryAccess( + const url::Origin &origin, const base::FilePath &path, HandleType handle_type, + content::GlobalRenderFrameHostId frame_id, + base::OnceCallback<void(SensitiveDirectoryResult)> callback, bool should_block); + + 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..f5ee4423f --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_grant_qt.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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) +{ +} + +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() != PermissionStatus::ASK || !m_context) { + if (GetStatus() == PermissionStatus::GRANTED) + SetStatus(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(PermissionStatus status) +{ + bool should_notify = m_status != status; + m_status = status; + if (should_notify) + NotifyPermissionStatusChanged(); +} + +void FileSystemAccessPermissionGrantQt::OnPermissionRequestResult( + base::OnceCallback<void(PermissionRequestOutcome)> callback, PermissionAction result) +{ + switch (result) { + case PermissionAction::GRANTED: + SetStatus(PermissionStatus::GRANTED); + std::move(callback).Run(PermissionRequestOutcome::kUserGranted); + break; + case PermissionAction::DENIED: + SetStatus(PermissionStatus::DENIED); + std::move(callback).Run(PermissionRequestOutcome::kUserDenied); + break; + case PermissionAction::DISMISSED: + case PermissionAction::IGNORED: + std::move(callback).Run(PermissionRequestOutcome::kUserDismissed); + break; + case PermissionAction::REVOKED: + case PermissionAction::GRANTED_ONCE: + case 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..c7ea1b331 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_grant_qt.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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; +using blink::mojom::PermissionStatus; +using permissions::PermissionAction; + +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: + 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(PermissionStatus status); + +private: + void OnPermissionRequestResult(base::OnceCallback<void(PermissionRequestOutcome)> callback, + 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. + PermissionStatus m_status = 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..65610dff9 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_controller.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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(std::move(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..d5caa37b7 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_controller_impl.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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()), 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..d2cc5ba2b --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_controller_impl.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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..594c879f2 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_manager_qt.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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) +{ +} + +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()->IsDocumentOnLoadCompletedInMainFrame() && !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( + QSharedPointer<FileSystemAccessPermissionRequestControllerImpl>::create( + m_currentRequest->data, + base::BindOnce( + &FileSystemAccessPermissionRequestManagerQt::OnPermissionDialogResult, + m_weakFactory.GetWeakPtr()))); + client->runFileSystemAccessRequest(std::move(request)); +} + +void FileSystemAccessPermissionRequestManagerQt::DocumentOnLoadCompletedInMainFrame( + content::RenderFrameHost *) +{ + // 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->GetPreviousMainFrameURL()); + 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()->GetMainFrame()->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..6746899b6 --- /dev/null +++ b/src/core/file_system_access/file_system_access_permission_request_manager_qt.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_MANAGER_QT_H +#define FILE_SYSTEM_ACCESS_PERMISSION_REQUEST_MANAGER_QT_H + +#include "base/callback_helpers.h" +#include "base/containers/circular_deque.h" +#include "base/files/file_path.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 DocumentOnLoadCompletedInMainFrame(content::RenderFrameHost *) 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 diff --git a/src/core/profile_qt.cpp b/src/core/profile_qt.cpp index 71198b2fc..829f96249 100644 --- a/src/core/profile_qt.cpp +++ b/src/core/profile_qt.cpp @@ -42,6 +42,7 @@ #include "profile_adapter.h" #include "browsing_data_remover_delegate_qt.h" #include "download_manager_delegate_qt.h" +#include "file_system_access/file_system_access_permission_context_factory_qt.h" #include "net/ssl_host_state_delegate_qt.h" #include "permission_manager_qt.h" #include "platform_notification_service_qt.h" @@ -240,6 +241,11 @@ std::string ProfileQt::GetMediaDeviceIDSalt() return m_prefServiceAdapter.mediaDeviceIdSalt(); } +content::FileSystemAccessPermissionContext *ProfileQt::GetFileSystemAccessPermissionContext() +{ + return FileSystemAccessPermissionContextFactoryQt::GetForProfile(this); +} + void ProfileQt::setupPrefService() { // Remove previous handler before we set a new one or we will assert diff --git a/src/core/profile_qt.h b/src/core/profile_qt.h index b9c2e0489..9fb1e7f04 100644 --- a/src/core/profile_qt.h +++ b/src/core/profile_qt.h @@ -91,6 +91,7 @@ public: content::StorageNotificationService *GetStorageNotificationService() override; content::PlatformNotificationService *GetPlatformNotificationService() override; std::string GetMediaDeviceIDSalt() override; + content::FileSystemAccessPermissionContext *GetFileSystemAccessPermissionContext() override; // Profile implementation: PrefService *GetPrefs() override; diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index b8d91af2d..2729e7beb 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -63,6 +63,7 @@ QT_FORWARD_DECLARE_CLASS(QKeyEvent) QT_FORWARD_DECLARE_CLASS(QVariant) +QT_FORWARD_DECLARE_CLASS(QWebEngineFileSystemAccessRequest) QT_FORWARD_DECLARE_CLASS(QWebEngineFindTextResult) QT_FORWARD_DECLARE_CLASS(QWebEngineLoadingInfo) QT_FORWARD_DECLARE_CLASS(QWebEngineQuotaRequest) @@ -223,6 +224,7 @@ public: virtual void runMouseLockPermissionRequest(const QUrl &securityOrigin) = 0; virtual void runQuotaRequest(QWebEngineQuotaRequest) = 0; virtual void runRegisterProtocolHandlerRequest(QWebEngineRegisterProtocolHandlerRequest) = 0; + virtual void runFileSystemAccessRequest(QWebEngineFileSystemAccessRequest) = 0; virtual QWebEngineSettings *webEngineSettings() const = 0; RenderProcessTerminationStatus renderProcessExitStatus(int); virtual void renderProcessTerminated(RenderProcessTerminationStatus terminationStatus, int exitCode) = 0; diff --git a/src/webenginequick/api/qquickwebengineview.cpp b/src/webenginequick/api/qquickwebengineview.cpp index 5d73f55bc..d2e5f9761 100644 --- a/src/webenginequick/api/qquickwebengineview.cpp +++ b/src/webenginequick/api/qquickwebengineview.cpp @@ -66,6 +66,7 @@ #include "web_contents_adapter.h" #include <QtWebEngineCore/qwebenginecertificateerror.h> +#include <QtWebEngineCore/qwebenginefilesystemaccessrequest.h> #include <QtWebEngineCore/qwebenginefindtextresult.h> #include <QtWebEngineCore/qwebenginefullscreenrequest.h> #include <QtWebEngineCore/qwebengineloadinginfo.h> @@ -647,6 +648,13 @@ void QQuickWebEngineViewPrivate::runRegisterProtocolHandlerRequest(QWebEngineReg Q_EMIT q->registerProtocolHandlerRequested(request); } +void QQuickWebEngineViewPrivate::runFileSystemAccessRequest( + QWebEngineFileSystemAccessRequest request) +{ + Q_Q(QQuickWebEngineView); + Q_EMIT q->fileSystemAccessRequested(request); +} + QObject *QQuickWebEngineViewPrivate::accessibilityParentObject() { Q_Q(QQuickWebEngineView); diff --git a/src/webenginequick/api/qquickwebengineview_p.h b/src/webenginequick/api/qquickwebengineview_p.h index 867e37d06..fb736085b 100644 --- a/src/webenginequick/api/qquickwebengineview_p.h +++ b/src/webenginequick/api/qquickwebengineview_p.h @@ -79,6 +79,7 @@ class QQuickWebEngineTooltipRequest; class QQuickWebEngineViewPrivate; class QWebEngineCertificateError; class QWebEngineContextMenuRequest; +class QWebEngineFileSystemAccessRequest; class QWebEngineFindTextResult; class QWebEngineFullScreenRequest; class QWebEngineHistory; @@ -572,6 +573,7 @@ Q_SIGNALS: Q_REVISION(1,12) void newWindowRequested(QQuickWebEngineNewWindowRequest *request); Q_REVISION(6,3) void touchSelectionMenuRequested(QQuickWebEngineTouchSelectionMenuRequest *request); Q_REVISION(6,4) void touchHandleDelegateChanged(); + Q_REVISION(6,4) void fileSystemAccessRequested(const QWebEngineFileSystemAccessRequest &request); protected: void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; diff --git a/src/webenginequick/api/qquickwebengineview_p_p.h b/src/webenginequick/api/qquickwebengineview_p_p.h index 073d0180c..2096f79c6 100644 --- a/src/webenginequick/api/qquickwebengineview_p_p.h +++ b/src/webenginequick/api/qquickwebengineview_p_p.h @@ -141,6 +141,7 @@ public: void runMouseLockPermissionRequest(const QUrl &securityOrigin) override; void runQuotaRequest(QWebEngineQuotaRequest) override; void runRegisterProtocolHandlerRequest(QWebEngineRegisterProtocolHandlerRequest) override; + void runFileSystemAccessRequest(QWebEngineFileSystemAccessRequest) override; QObject *accessibilityParentObject() override; QWebEngineSettings *webEngineSettings() const override; void allowCertificateError(const QWebEngineCertificateError &error) override; diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp index edec675f6..3b7da9759 100644 --- a/tests/auto/quick/publicapi/tst_publicapi.cpp +++ b/tests/auto/quick/publicapi/tst_publicapi.cpp @@ -35,6 +35,7 @@ #include <QtTest/QtTest> #include <QtWebEngineQuick/QQuickWebEngineProfile> #include <QtWebEngineCore/QWebEngineCertificateError> +#include <QtWebEngineCore/QWebEngineFileSystemAccessRequest> #include <QtWebEngineCore/QWebEngineFindTextResult> #include <QtWebEngineCore/QWebEngineFullScreenRequest> #include <QtWebEngineCore/QWebEngineHistory> @@ -85,6 +86,7 @@ static const QList<const QMetaObject *> typesToCheck = QList<const QMetaObject * << &QQuickWebEngineTooltipRequest::staticMetaObject << &QWebEngineContextMenuRequest::staticMetaObject << &QWebEngineCertificateError::staticMetaObject + << &QWebEngineFileSystemAccessRequest::staticMetaObject << &QWebEngineFindTextResult::staticMetaObject << &QWebEngineLoadingInfo::staticMetaObject << &QWebEngineNavigationRequest::staticMetaObject @@ -288,6 +290,16 @@ static const QStringList expectedAPI = QStringList() << "QWebEngineFullScreenRequest.origin --> QUrl" << "QWebEngineFullScreenRequest.reject() --> void" << "QWebEngineFullScreenRequest.toggleOn --> bool" + << "QWebEngineFileSystemAccessRequest.File --> HandleType" + << "QWebEngineFileSystemAccessRequest.Directory --> HandleType" + << "QWebEngineFileSystemAccessRequest.Read --> AccessFlags" + << "QWebEngineFileSystemAccessRequest.Write --> AccessFlags" + << "QWebEngineFileSystemAccessRequest.origin --> QUrl" + << "QWebEngineFileSystemAccessRequest.filePath --> QUrl" + << "QWebEngineFileSystemAccessRequest.handleType --> QWebEngineFileSystemAccessRequest::HandleType" + << "QWebEngineFileSystemAccessRequest.accessFlags --> QFlags<QWebEngineFileSystemAccessRequest::AccessFlag>" + << "QWebEngineFileSystemAccessRequest.accept() --> void" + << "QWebEngineFileSystemAccessRequest.reject() --> void" << "QWebEngineHistory.backItems --> QWebEngineHistoryModel*" << "QWebEngineHistory.clear() --> void" << "QWebEngineHistory.forwardItems --> QWebEngineHistoryModel*" @@ -697,6 +709,7 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.devToolsViewChanged() --> void" << "QQuickWebEngineView.featurePermissionRequested(QUrl,Feature) --> void" << "QQuickWebEngineView.fileDialogRequested(QQuickWebEngineFileDialogRequest*) --> void" + << "QQuickWebEngineView.fileSystemAccessRequested(QWebEngineFileSystemAccessRequest) --> void" << "QQuickWebEngineView.findText(QString) --> void" << "QQuickWebEngineView.findText(QString,FindFlags) --> void" << "QQuickWebEngineView.findText(QString,FindFlags,QJSValue) --> void" diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index b16e9fa62..57f6b24a8 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -48,6 +48,7 @@ #include <qnetworkreply.h> #include <qnetworkrequest.h> #include <qwebenginedownloadrequest.h> +#include <qwebenginefilesystemaccessrequest.h> #include <qwebenginefindtextresult.h> #include <qwebenginefullscreenrequest.h> #include <qwebenginehistory.h> @@ -4794,17 +4795,17 @@ void tst_QWebEnginePage::renderProcessPid() class FileSelectionTestPage : public QWebEnginePage { public: - FileSelectionTestPage() - { } + FileSelectionTestPage() : m_tempDir(QDir::tempPath() + "/tst_qwebenginepage-XXXXXX") { } QStringList chooseFiles(FileSelectionMode mode, const QStringList &oldFiles, const QStringList &acceptedMimeTypes) override { Q_UNUSED(oldFiles); chosenFileSelectionMode = mode; chosenAcceptedMimeTypes = acceptedMimeTypes; - return QStringList(); + return QStringList() << (m_tempDir.path() + "/file.txt"); } + QTemporaryDir m_tempDir; int chosenFileSelectionMode = -1; QStringList chosenAcceptedMimeTypes; }; @@ -4890,9 +4891,27 @@ void tst_QWebEnginePage::fileSystemAccessDialog() view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); - page.setHtml(QString("<html><body>" + connect(&page, &QWebEnginePage::fileSystemAccessRequested, + [](QWebEngineFileSystemAccessRequest request) { + QCOMPARE(request.accessFlags(), + QWebEngineFileSystemAccessRequest::Read + | QWebEngineFileSystemAccessRequest::Write); + request.accept(); + }); + + page.setHtml(QString("<html><head><script>" + "async function getTemporaryDir() {" + " const newHandle = await window.showSaveFilePicker();" + " const writable = await newHandle.createWritable();" + " await writable.write(new Blob(['New value']));" + " await writable.close();" + "" + " const fileData = await newHandle.getFile();" + " document.title = await fileData.text();" + "}" + "</script></head><body>" "<button id='triggerDialog' value='trigger' " - "onclick='window.showDirectoryPicker()'>" + "onclick='getTemporaryDir()'" "</body></html>"), QString("qrc:/")); QVERIFY(spyFinished.wait()); @@ -4903,7 +4922,9 @@ void tst_QWebEnginePage::fileSystemAccessDialog() QStringLiteral("triggerDialog")); QTest::keyClick(view.focusProxy(), Qt::Key_Enter); - QTRY_COMPARE(page.chosenFileSelectionMode, QWebEnginePage::FileSelectUploadFolder); + QTRY_COMPARE(page.title(), "New value"); + + QTRY_COMPARE(page.chosenFileSelectionMode, QWebEnginePage::FileSelectSave); QTRY_COMPARE(page.chosenAcceptedMimeTypes, QStringList()); } |