File indexing completed on 2024-12-22 04:43:20
0001 /* 0002 This file is part of the KDE project. 0003 0004 SPDX-FileCopyrightText: 2018 Stefano Crocco <stefano.crocco@alice.it> 0005 0006 SPDX-License-Identifier: LGPL-2.1-or-later 0007 */ 0008 0009 #ifndef WEBENGINEPARTCOOKIEJAR_KIO_H 0010 #define WEBENGINEPARTCOOKIEJAR_KIO_H 0011 0012 #include "qtwebengine6compat.h" 0013 0014 #include <QObject> 0015 #include <QNetworkCookie> 0016 #include <QHash> 0017 #include <QUrl> 0018 #include <QWebEngineCookieStore> 0019 #include <QStringList> 0020 #include <QVariant> 0021 #include <QVector> 0022 #include <QDBusInterface> 0023 #include <QSet> 0024 0025 #include "kwebenginepartlib_export.h" 0026 0027 class QDebug; 0028 class QWidget; 0029 class QWebEngineProfile; 0030 class QDBusPendingCallWatcher; 0031 0032 /** 0033 * @brief Class which takes care of synchronizing Chromium cookies from `QWebEngineCookieStore` with KIO 0034 */ 0035 class KWEBENGINEPARTLIB_EXPORT WebEnginePartCookieJarKIO : public QObject 0036 { 0037 Q_OBJECT 0038 0039 public: 0040 0041 /** 0042 * @brief Constructor 0043 * 0044 * @param [in,out] prof the profile containing the store to synchronize with 0045 * @param parent the parent object 0046 * @note The `PersistentCookiePolicy` of the given profile will be set to `NoPersistentCookies` so that, on application startup, 0047 * only cookies from KIO will be inside the store 0048 */ 0049 WebEnginePartCookieJarKIO(QWebEngineProfile* prof, QObject* parent = nullptr); 0050 0051 /** 0052 * @brief Destructor 0053 */ 0054 ~WebEnginePartCookieJarKIO() override; 0055 0056 private slots: 0057 0058 /** 0059 * @brief Adds a cookie to KIO 0060 * 0061 * This slot is called in response to `QWebEngineCookieStore::cookieAdded` signal. 0062 * 0063 * @param cookie cookie the cookie to add 0064 * 0065 * @internal KIO requires an URL when adding a cookie; unfortunately, the `cookieAdded` signal doesn't provide one. To solve this problem, an URL is created 0066 * by using the scheme of #m_lastRequestOrigin and the cookie's domain. Looking At the source code for `KCookieJar`, an URL built this way should work. 0067 * In case the cookie's domain is empty (which I believe shouldn't happen), #m_lastRequestOrigin is used. 0068 * 0069 * This function also adds the cookie and its corresponding URL to #m_cookiesUrl 0070 */ 0071 void addCookie(const QNetworkCookie &cookie); 0072 0073 /** 0074 * @brief Removes the given cookie from KIO 0075 * 0076 * This slot is called in response to `QWebEngineCookieStore::cookieRemoved` signal. 0077 * 0078 * @param cookie the cookie to remove 0079 * 0080 * @internal As for addCookie() there's a problem here, because `QWebEngineCookieStore::cookieRemoved` doesn't provide a fqdn for the cookie. This value is obtained 0081 * from #m_cookiesUrl. 0082 */ 0083 void removeCookie(const QNetworkCookie &cookie); 0084 0085 /** 0086 * @brief Removes all session cookies from `KCookieJar` 0087 * 0088 * This function doesn't remove cookies from the `QWebEngineCookieStore`, because it's meant to be called 0089 * when the application is closing, so those cookies will be destroyed automatically (because they're only stored 0090 * in memory) 0091 * 0092 */ 0093 void deleteSessionCookies(); 0094 0095 /** 0096 * @brief Slot called when the DBus call to `KCookieServer::deleteCookie` fails 0097 * 0098 * It displays the error message on standard error 0099 * 0100 * @param watcher the object describing the result of the DBus call 0101 */ 0102 void cookieRemovalFailed(QDBusPendingCallWatcher *watcher); 0103 0104 private: 0105 0106 /** 0107 * @brief Determine the window ID to pass to `KCookieServer::addCookies` 0108 * 0109 * There's no sure way to find the window which received a cookie, so this function chooses the active window 0110 * if there's one (according to `QApplication::activeWindow`) or the first widget in `QApplication::topLevelWidgets` 0111 * for which `QWidget::isWindow` is `true` and which doesn't have a parent. 0112 * 0113 * @return the ID of the window determined as described above or 0 if no such a window can be found 0114 */ 0115 static qlonglong findWinID(); 0116 0117 struct CookieWithUrl { 0118 QNetworkCookie cookie; 0119 QUrl url; 0120 }; 0121 0122 using CookieUrlList = QVector<CookieWithUrl>; 0123 using CookieList = QVector<QNetworkCookie>; 0124 0125 /** 0126 * @brief An identifier for a cookie 0127 * 0128 * The identifier is made by the cookie's name, domain and path 0129 */ 0130 struct CookieIdentifier{ 0131 0132 /** 0133 * @brief Default constructor 0134 */ 0135 CookieIdentifier(){} 0136 0137 /** 0138 * @brief Constructor 0139 * 0140 * @param cookie the cookie to create the identifier for 0141 */ 0142 CookieIdentifier(const QNetworkCookie &cookie); 0143 0144 /** 0145 * @brief Constructor 0146 * 0147 * @param n the cookie's name 0148 * @param d the cookie's domain 0149 * @param p the cookie's path 0150 */ 0151 CookieIdentifier(const QString &n, const QString &d, const QString &p); 0152 0153 /** 0154 * Comparison operator 0155 * 0156 * Two cookies are equal if all their fields are equal 0157 * @param other the identifier to compare this identifier to 0158 * @return `true` if the two identifiers' name, domain and path are equal and `false` if at least one of them is different 0159 */ 0160 bool operator== (const CookieIdentifier &other) const {return name == other.name && domain == other.domain && path == other.path;} 0161 0162 /** 0163 * @brief The name of the cookie 0164 * 0165 */ 0166 QString name; 0167 /** 0168 * @brief The domain of the cookie 0169 * 0170 */ 0171 QString domain; 0172 0173 /** 0174 * @brief The path of the cookie 0175 * 0176 */ 0177 QString path; 0178 0179 }; 0180 0181 friend QDebug operator<<(QDebug, const CookieIdentifier &); 0182 0183 using CookieIdentifierList = QList<CookieIdentifier>; 0184 0185 /** 0186 * @brief Checks whether a cookie with the given identifier is in the KCookieJar 0187 * 0188 * @note If the _id_'s domain doesn't start with a dot, a dot is prepended to it. This is because 0189 * `KCookieJar` always wants a dot in front of the domain. 0190 * 0191 * @param id the identifiere of the cookie 0192 * @param url the origin of the cookie 0193 * @return `true` if a cookie with the given identifier is in the KCookieJar and `false` otherwise 0194 */ 0195 bool cookieInKCookieJar(const CookieIdentifier &id, const QUrl &url); 0196 0197 /** 0198 * @brief The advice from `KCookieJar` for a URL 0199 * 0200 * @param url The URL to get the advice for 0201 * @return The advice for _url_. It can be one of `"Accept"`, `"AcceptForSession"`, `"Reject"`, `"Ask"`, `"Dunno"` or an empty 0202 * string if an error happens while contacting the `KCookieServer` 0203 */ 0204 QString askAdvice(const QUrl &url); 0205 0206 /** 0207 * @brief Inserts all cookies contained in `KCookieJar` to the store 0208 * 0209 * @note this function should only be called by the constructor 0210 * @note this function is asynchronous because it calls `QWebEngineCookieStore::setCookie` 0211 */ 0212 void loadKIOCookies(); 0213 0214 /** 0215 * @brief Finds all cookies stored in `KCookieJar` 0216 * @return a list of the cookies in `KCookieJar` 0217 */ 0218 CookieUrlList findKIOCookies(); 0219 0220 /** 0221 * @brief Enum describing the possible fields to pas to `KCookieServer::findCookies` using DBus. 0222 * 0223 * The values are the same as those of `CookieDetails` in `kio/src/kioslaves/http/kcookiejar/kcookieserver.cpp` 0224 */ 0225 enum class CookieDetails {domain=0, path=1, name=2, host=3, value=4, expirationDate=5, protocolVersion=6, secure=7}; 0226 0227 /** 0228 * @brief Parses the value returned by `KCookieServer::findCookies` for a single cookie 0229 * 0230 * This function assumes that all possible data for the cookie is available (that is, that the list returned by 0231 * `KCookieServer::findCookies` contains an entry for each value in #CookieDetails) 0232 * 0233 * @param data: the data returned by `KCookieServer::findCookies`. It can contain data for more than one cookie, but only one will be parsed 0234 * @param start: the position in the list where the data for the cookie starts. 0235 * @return The cookie described by the data and its host 0236 */ 0237 static CookieWithUrl parseKIOCookie(const QStringList &data, int start); 0238 0239 /** 0240 * @brief Function used to filter cookies 0241 * 0242 * In theory, this function should use the configuration chosen by the user in the Cookies KCM. However, this can't be done for several reasons: 0243 * - this function doesn't have the cookies details and they're needed for the "Ask" policy 0244 * - this function doesn't know the URL of the cookie (even if `QWebEngineCookieStore::FilterRequest::origin` could be used as a substitute 0245 * - if the policy is "Ask" and the question was asked here, it would be asked again when adding the cookie to `KCookieJar`. 0246 * Because of these reasons, the only setting from the KCM which is applied here is whether to accept and reject cross domain cookies. Other settings 0247 * from the KCM will be enforced by the addCookies(). 0248 * 0249 * @param req the request to filter 0250 * @return ``false` for third party cookies if the user chose to block them in the KCM and `true` otherwise 0251 * @internal Besides filtering cookie requests, this function also stores the `origin` member of request in the #m_lastRequestOrigin. 0252 * @endinternal 0253 * @sa addCookie() 0254 */ 0255 bool filterCookie(const QWebEngineCookieStore::FilterRequest &req); 0256 0257 /** 0258 * @brief Removes the domain from the cookie if the domain doesn't start with a dot 0259 * 0260 * The cookies managed by QWebEngine never have an empty domain; instead, their domain starts with a dot if it was explicitly given and doesn't start 0261 * with a dot in case it wasn't explicitly given. `KCookieServer::addCookies` and `KCookieServer::deleteCookie`, instead, require an empty domain if the 0262 * domain wasn't explicitly given. This function transforms a cookie of the first form to one of the second form. 0263 * 0264 * If the cookie's domain starts with a dot, this function does nothing. 0265 * 0266 * @param cookie the cookie to remove the domain from. This cookie is modified in place 0267 */ 0268 static void removeCookieDomain(QNetworkCookie &cookie); 0269 0270 /** 0271 * @brief Tries to construct an Url for the given cookie 0272 * 0273 * The URL is meant to be passed to functions like `KCookieServer::addCookies` and is constructed from the cookie's domain 0274 * if it's not empty. If the domain is empty or it's only a dot, an empty URL is returned 0275 * 0276 * @param cookie the cookie to create the URL for 0277 * @return the URL for the cookie or an empty URL if the cookie's domain is empty or it's only a dot 0278 */ 0279 QUrl constructUrlForCookie(const QNetworkCookie &cookie) const; 0280 0281 /** 0282 * @brief The `QWebEngineCookieStore` to synchronize KIO with 0283 */ 0284 QWebEngineCookieStore* m_cookieStore; 0285 0286 /** 0287 * @brief The DBus interface used to connect to KCookieServer 0288 */ 0289 QDBusInterface m_cookieServer; 0290 0291 /** 0292 * @brief The fields to pass to `KCookieStore::findCookies` via DBus 0293 */ 0294 static const QVariant s_findCookieFields; 0295 0296 /** 0297 * @brief Overload of `qHash` for a CookieIdentifier 0298 * 0299 * @param id: the other identifier 0300 * @param seed: the seed 0301 * @return The hash value of the identifier 0302 */ 0303 friend qHashReturnType qHash(const CookieIdentifier &id, uint seed){return qHash(QStringList{id.name, id.domain, id.path}, seed);}; 0304 0305 /** 0306 * @brief A list of cookies which were added to the `QWebEngineCookieStore` but were rejected by KCookieJar and must 0307 * be removed from the store 0308 * 0309 * When cookieRemoved() is called with one of those cookies, the cookie is removed from this list and no attempt is made to remove 0310 * the cookie from `KCookieJar` (because it's not there) 0311 */ 0312 QVector<CookieIdentifier> m_pendingRejectedCookies; 0313 0314 /** 0315 * @brief The IDs of all the windows which have session cookies 0316 */ 0317 QSet<qlonglong> m_windowsWithSessionCookies; 0318 0319 /** 0320 * @brief A list of cookies loaded from KCookieServer when this instance is created 0321 */ 0322 CookieList m_cookiesLoadedFromKCookieServer; 0323 0324 #ifdef BUILD_TESTING 0325 QList<QNetworkCookie> m_testCookies; 0326 friend class TestWebEnginePartCookieJarKIO; 0327 #endif 0328 0329 }; 0330 0331 /** 0332 * @brief Overload of operator `<<` to allow a WebEnginePartCookieJarKIO::CookieIdentifier to be written to a `QDebug` 0333 * 0334 * @param deb the debug object 0335 * @param id the identifier to write 0336 * @return the debug object 0337 */ 0338 QDebug operator<<(QDebug deb, const WebEnginePartCookieJarKIO::CookieIdentifier &id); 0339 0340 #endif // WEBENGINEPARTCOOKIEJAR_KIO_H