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"