File indexing completed on 2025-01-19 03:53:02
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2017-06-21 0007 * Description : a simple web browser dialog based on Qt WebEngine. 0008 * 0009 * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2021 by Joerg Lohse <joergmlpts at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "inatbrowserdlg.h" 0017 #include "digikam_config.h" 0018 0019 // Qt includes 0020 0021 #include <QGridLayout> 0022 #include <QApplication> 0023 #include <QStyle> 0024 #include <QIcon> 0025 #include <QToolBar> 0026 #include <QDesktopServices> 0027 #include <QJsonDocument> 0028 #include <QHash> 0029 0030 #ifdef HAVE_QWEBENGINE 0031 # include <QWebEngineView> 0032 # include <QWebEnginePage> 0033 # include <QWebEngineProfile> 0034 # include <QWebEngineCookieStore> 0035 #else 0036 # include <qwebview.h> 0037 # include <QWebFrame> 0038 # include <QNetworkCookieJar> 0039 #endif 0040 0041 // KDE includes 0042 0043 #include <kconfiggroup.h> 0044 #include <ksharedconfig.h> 0045 #include <klocalizedstring.h> 0046 0047 // Local includes 0048 0049 #include "statusprogressbar.h" 0050 #include "searchtextbar.h" 0051 #include "dxmlguiwindow.h" 0052 #include "digikam_debug.h" 0053 0054 namespace DigikamGenericINatPlugin 0055 { 0056 0057 #ifndef HAVE_QWEBENGINE 0058 0059 /** 0060 * Derived class that exposes protected function allCookies(). 0061 */ 0062 class InatBrowserCookieJar : public QNetworkCookieJar 0063 { 0064 public: 0065 0066 InatBrowserCookieJar(QObject* const parent = nullptr) 0067 : QNetworkCookieJar(parent) 0068 { 0069 } 0070 0071 ~InatBrowserCookieJar() 0072 { 0073 } 0074 0075 QList<QNetworkCookie> getAllCookies() const 0076 { 0077 return allCookies(); 0078 } 0079 }; 0080 0081 #endif // !defined HAVE_QWEBENGINE 0082 0083 class Q_DECL_HIDDEN INatBrowserDlg::Private 0084 { 0085 public: 0086 0087 explicit Private() 0088 : home (QLatin1String("https://www.inaturalist.org/users/api_token")), 0089 browser (nullptr), 0090 toolbar (nullptr), 0091 apiKeyFound(false) 0092 { 0093 } 0094 0095 public: 0096 0097 const QUrl home; 0098 0099 #ifdef HAVE_QWEBENGINE 0100 0101 QWebEngineView* browser; 0102 0103 #else 0104 0105 QWebView* browser; 0106 0107 #endif 0108 0109 QString username; 0110 QToolBar* toolbar; 0111 bool apiKeyFound; 0112 0113 QHash<QByteArray, QNetworkCookie> cookies; 0114 }; 0115 0116 INatBrowserDlg::INatBrowserDlg(const QString& username, 0117 const QList<QNetworkCookie>& cookies, 0118 QWidget* const parent) 0119 : QDialog(parent), 0120 d (new Private) 0121 { 0122 setModal(false); 0123 d->username = username; 0124 0125 #ifdef HAVE_QWEBENGINE 0126 0127 d->browser = new QWebEngineView(this); 0128 QWebEngineCookieStore* const cookieJar = d->browser->page()->profile()->cookieStore(); 0129 cookieJar->deleteAllCookies(); 0130 0131 connect(cookieJar, SIGNAL(cookieAdded(QNetworkCookie)), 0132 this, SLOT(slotCookieAdded(QNetworkCookie))); 0133 0134 connect(cookieJar, SIGNAL(cookieRemoved(QNetworkCookie)), 0135 this, SLOT(slotCookieRemoved(QNetworkCookie))); 0136 0137 #else 0138 0139 d->browser = new QWebView(this); 0140 d->browser->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); 0141 QNetworkCookieJar* const cookieJar = new InatBrowserCookieJar(); 0142 d->browser->page()->networkAccessManager()->setCookieJar(cookieJar); 0143 0144 #endif 0145 0146 QDateTime now(QDateTime::currentDateTime()); 0147 0148 for (const auto& cookie : cookies) 0149 { 0150 if (filterCookie(cookie, false, now)) 0151 { 0152 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Setting cookie" << cookie; 0153 0154 #ifdef HAVE_QWEBENGINE 0155 0156 cookieJar->setCookie(cookie); 0157 0158 #else 0159 0160 cookieJar->insertCookie(cookie); 0161 0162 #endif 0163 0164 } 0165 } 0166 0167 // -------------------------- 0168 0169 d->toolbar = new QToolBar(this); 0170 d->toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0171 0172 #ifdef HAVE_QWEBENGINE 0173 0174 d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Back)); 0175 d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Forward)); 0176 d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Reload)); 0177 d->toolbar->addAction(d->browser->pageAction(QWebEnginePage::Stop)); 0178 0179 #else 0180 0181 d->toolbar->addAction(d->browser->pageAction(QWebPage::Back)); 0182 d->toolbar->addAction(d->browser->pageAction(QWebPage::Forward)); 0183 d->toolbar->addAction(d->browser->pageAction(QWebPage::Reload)); 0184 d->toolbar->addAction(d->browser->pageAction(QWebPage::Stop)); 0185 0186 #endif 0187 0188 QAction* const gohome = new QAction(QIcon::fromTheme(QLatin1String("go-home")), 0189 i18n("Home"), this); 0190 gohome->setToolTip(i18n("Go back to Home page")); 0191 d->toolbar->addAction(gohome); 0192 0193 // ---------------------- 0194 0195 QGridLayout* const grid = new QGridLayout(this); 0196 grid->setSpacing(qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0197 QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing))); 0198 grid->addWidget(d->toolbar, 0, 0, 1, 1); 0199 grid->addWidget(d->browser, 1, 0, 1, 3); 0200 grid->setColumnStretch(1, 10); 0201 grid->setRowStretch(1, 10); 0202 setLayout(grid); 0203 0204 // ---------------------- 0205 0206 connect(d->browser, SIGNAL(titleChanged(QString)), 0207 this, SLOT(slotTitleChanged(QString))); 0208 0209 connect(d->browser, SIGNAL(loadFinished(bool)), 0210 this, SLOT(slotLoadingFinished(bool))); 0211 0212 connect(gohome, SIGNAL(triggered()), 0213 this, SLOT(slotGoHome())); 0214 0215 connect(this, SIGNAL(signalWebText(QString)), 0216 this, SLOT(slotWebText(QString))); 0217 0218 // ---------------------- 0219 0220 resize(800, 800); 0221 0222 slotGoHome(); 0223 } 0224 0225 INatBrowserDlg::~INatBrowserDlg() 0226 { 0227 delete d; 0228 } 0229 0230 void INatBrowserDlg::closeEvent(QCloseEvent* e) 0231 { 0232 if (!d->apiKeyFound) 0233 { 0234 Q_EMIT signalApiToken(QString(), QList<QNetworkCookie>()); 0235 } 0236 0237 e->accept(); 0238 } 0239 0240 void INatBrowserDlg::slotTitleChanged(const QString& title) 0241 { 0242 setWindowTitle(title); 0243 } 0244 0245 void INatBrowserDlg::slotLoadingFinished(bool status) 0246 { 0247 QString url = d->browser->url().toString(); 0248 0249 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Url" << url << "loaded."; 0250 0251 if (status && (url == d->home.toString())) 0252 { 0253 0254 #ifdef HAVE_QWEBENGINE 0255 0256 d->browser->page()->toPlainText( 0257 [this](const QString& text) 0258 { 0259 Q_EMIT signalWebText(text); 0260 } 0261 ); 0262 0263 #else 0264 0265 Q_EMIT signalWebText(d->browser->page()->mainFrame()->toPlainText()); 0266 0267 #endif 0268 0269 } 0270 else if (!d->username.isEmpty() && 0271 (url == QLatin1String("https://www.inaturalist.org/users/sign_in"))) 0272 { 0273 QString script = QString(QLatin1String("document.getElementById(\"user_" 0274 "email\").value=\"%1\";")). 0275 arg(d->username); 0276 0277 #ifdef HAVE_QWEBENGINE 0278 0279 d->browser->page()->runJavaScript(script); 0280 0281 #else 0282 0283 d->browser->page()->mainFrame()->evaluateJavaScript(script); 0284 0285 #endif 0286 0287 } 0288 } 0289 0290 void INatBrowserDlg::slotWebText(const QString& text) 0291 { 0292 const QString key = QLatin1String("api_token"); 0293 0294 QJsonParseError err; 0295 QJsonDocument doc = QJsonDocument::fromJson(text.toUtf8(), &err); 0296 0297 if ((err.error == QJsonParseError::NoError) && doc.isObject() && 0298 doc.object().contains(key)) 0299 { 0300 0301 #ifdef HAVE_QWEBENGINE 0302 0303 Q_EMIT signalApiToken(doc.object()[key].toString(), 0304 filterCookies(d->cookies.values(), false)); 0305 0306 #else 0307 0308 const InatBrowserCookieJar* jar = dynamic_cast<const InatBrowserCookieJar*> 0309 (d->browser->page()->networkAccessManager()->cookieJar()); 0310 0311 Q_EMIT signalApiToken(doc.object()[key].toString(), 0312 filterCookies(jar->getAllCookies(), false)); 0313 0314 #endif 0315 0316 d->apiKeyFound = true; 0317 close(); 0318 } 0319 } 0320 0321 void INatBrowserDlg::slotGoHome() 0322 { 0323 d->browser->setUrl(d->home); 0324 } 0325 0326 static QByteArray cookieKey(const QNetworkCookie& cookie) 0327 { 0328 static const char sep('\n'); 0329 0330 return ( 0331 cookie.name() + 0332 sep + 0333 cookie.domain().toUtf8() + 0334 sep + 0335 cookie.path().toUtf8() 0336 ); 0337 } 0338 0339 bool INatBrowserDlg::filterCookie(const QNetworkCookie& cookie, 0340 bool keepSessionCookies, 0341 const QDateTime& now) 0342 { 0343 return cookie.isSessionCookie() ? keepSessionCookies 0344 : (cookie.expirationDate() > now); 0345 } 0346 0347 QList<QNetworkCookie> INatBrowserDlg::filterCookies(const QList<QNetworkCookie>& cookies, 0348 bool keepSessionCookies) 0349 { 0350 QList<QNetworkCookie> result; 0351 QDateTime now(QDateTime::currentDateTime()); 0352 0353 for (const auto& cookie : cookies) 0354 { 0355 if (filterCookie(cookie, keepSessionCookies, now)) 0356 { 0357 result << cookie; 0358 } 0359 } 0360 0361 return result; 0362 } 0363 0364 void INatBrowserDlg::slotCookieAdded(const QNetworkCookie& cookie) 0365 { 0366 d->cookies.insert(cookieKey(cookie), cookie); 0367 } 0368 0369 void INatBrowserDlg::slotCookieRemoved(const QNetworkCookie& cookie) 0370 { 0371 d->cookies.remove(cookieKey(cookie)); 0372 } 0373 0374 } // namespace DigikamGenericINatPlugin 0375 0376 #include "moc_inatbrowserdlg.cpp"