File indexing completed on 2024-11-24 04:49:02

0001 /*
0002    SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "gravatarresolvurljob.h"
0008 #include "gravatar_debug.h"
0009 #include "misc/gravatarcache.h"
0010 #include "misc/hash.h"
0011 #include <PimCommon/NetworkManager>
0012 
0013 #include <QCryptographicHash>
0014 #include <QNetworkReply>
0015 #include <QUrlQuery>
0016 
0017 using namespace Gravatar;
0018 
0019 class Gravatar::GravatarResolvUrlJobPrivate
0020 {
0021 public:
0022     GravatarResolvUrlJobPrivate() = default;
0023     QPixmap mPixmap;
0024     QString mEmail;
0025     Hash mCalculatedHash;
0026     QNetworkAccessManager *mNetworkAccessManager = nullptr;
0027     int mSize = 80;
0028 
0029     enum Backend { None = 0x0, Libravatar = 0x1, Gravatar = 0x2 };
0030     int mBackends = Gravatar;
0031 
0032     bool mHasGravatar = false;
0033     bool mUseDefaultPixmap = false;
0034 };
0035 
0036 GravatarResolvUrlJob::GravatarResolvUrlJob(QObject *parent)
0037     : QObject(parent)
0038     , d(new Gravatar::GravatarResolvUrlJobPrivate)
0039 {
0040 }
0041 
0042 GravatarResolvUrlJob::~GravatarResolvUrlJob() = default;
0043 
0044 bool GravatarResolvUrlJob::canStart() const
0045 {
0046     if (PimCommon::NetworkManager::self()->isOnline()) {
0047         // qCDebug(GRAVATAR_LOG) << "email " << d->mEmail;
0048         return !d->mEmail.trimmed().isEmpty() && (d->mEmail.contains(QLatin1Char('@')));
0049     } else {
0050         return false;
0051     }
0052 }
0053 
0054 QUrl GravatarResolvUrlJob::generateGravatarUrl(bool useLibravatar)
0055 {
0056     return createUrl(useLibravatar);
0057 }
0058 
0059 bool GravatarResolvUrlJob::hasGravatar() const
0060 {
0061     return d->mHasGravatar;
0062 }
0063 
0064 void GravatarResolvUrlJob::startNetworkManager(const QUrl &url)
0065 {
0066     if (!d->mNetworkAccessManager) {
0067         d->mNetworkAccessManager = new QNetworkAccessManager(this);
0068         d->mNetworkAccessManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
0069         d->mNetworkAccessManager->setStrictTransportSecurityEnabled(true);
0070         d->mNetworkAccessManager->enableStrictTransportSecurityStore(true);
0071         connect(d->mNetworkAccessManager, &QNetworkAccessManager::finished, this, &GravatarResolvUrlJob::slotFinishLoadPixmap);
0072     }
0073 
0074     QNetworkRequest req(url);
0075     req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
0076     req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
0077     d->mNetworkAccessManager->get(req);
0078 }
0079 
0080 void GravatarResolvUrlJob::start()
0081 {
0082     if (d->mBackends == GravatarResolvUrlJobPrivate::None) {
0083         d->mBackends = GravatarResolvUrlJobPrivate::Gravatar; // default is Gravatar if nothing else is selected
0084     }
0085 
0086     d->mHasGravatar = false;
0087     if (canStart()) {
0088         processNextBackend();
0089     } else {
0090         qCDebug(GRAVATAR_LOG) << "Gravatar can not start";
0091         deleteLater();
0092     }
0093 }
0094 
0095 void GravatarResolvUrlJob::processNextBackend()
0096 {
0097     if (d->mHasGravatar || d->mBackends == GravatarResolvUrlJobPrivate::None) {
0098         Q_EMIT finished(this);
0099         deleteLater();
0100         return;
0101     }
0102 
0103     QUrl url;
0104     if (d->mBackends & GravatarResolvUrlJobPrivate::Libravatar) {
0105         d->mBackends &= ~GravatarResolvUrlJobPrivate::Libravatar;
0106         url = createUrl(true);
0107     } else if (d->mBackends & GravatarResolvUrlJobPrivate::Gravatar) {
0108         d->mBackends &= ~GravatarResolvUrlJobPrivate::Gravatar;
0109         url = createUrl(false);
0110     }
0111 
0112     // qDebug() << " url " << url;
0113     Q_EMIT resolvUrl(url);
0114     if (!cacheLookup(d->mCalculatedHash)) {
0115         startNetworkManager(url);
0116     } else {
0117         processNextBackend();
0118     }
0119 }
0120 
0121 void GravatarResolvUrlJob::slotFinishLoadPixmap(QNetworkReply *reply)
0122 {
0123     if (reply->error() == QNetworkReply::NoError) {
0124         const QByteArray data = reply->readAll();
0125         d->mPixmap.loadFromData(data);
0126         d->mHasGravatar = true;
0127         // For the moment don't use cache other we will store a lot of pixmap
0128         if (!d->mUseDefaultPixmap) {
0129             GravatarCache::self()->saveGravatarPixmap(d->mCalculatedHash, d->mPixmap);
0130         }
0131     } else {
0132         if (reply->error() != QNetworkReply::ContentNotFoundError) {
0133             GravatarCache::self()->saveMissingGravatar(d->mCalculatedHash);
0134         } else {
0135             qCDebug(GRAVATAR_LOG) << "Network error:" << reply->request().url() << reply->errorString();
0136         }
0137     }
0138     reply->deleteLater();
0139 
0140     processNextBackend();
0141 }
0142 
0143 QString GravatarResolvUrlJob::email() const
0144 {
0145     return d->mEmail;
0146 }
0147 
0148 void GravatarResolvUrlJob::setEmail(const QString &email)
0149 {
0150     d->mEmail = email;
0151 }
0152 
0153 Hash GravatarResolvUrlJob::calculateHash()
0154 {
0155     const auto email = d->mEmail.toLower().toUtf8();
0156     return Hash(QCryptographicHash::hash(email, QCryptographicHash::Md5), Hash::Md5);
0157 }
0158 
0159 bool GravatarResolvUrlJob::fallbackGravatar() const
0160 {
0161     return d->mBackends & GravatarResolvUrlJobPrivate::Gravatar;
0162 }
0163 
0164 void GravatarResolvUrlJob::setFallbackGravatar(bool fallbackGravatar)
0165 {
0166     if (fallbackGravatar) {
0167         d->mBackends |= GravatarResolvUrlJobPrivate::Gravatar;
0168     } else {
0169         d->mBackends &= ~GravatarResolvUrlJobPrivate::Gravatar;
0170     }
0171 }
0172 
0173 bool GravatarResolvUrlJob::useLibravatar() const
0174 {
0175     return d->mBackends & GravatarResolvUrlJobPrivate::Libravatar;
0176 }
0177 
0178 void GravatarResolvUrlJob::setUseLibravatar(bool useLibravatar)
0179 {
0180     if (useLibravatar) {
0181         d->mBackends |= GravatarResolvUrlJobPrivate::Libravatar;
0182     } else {
0183         d->mBackends &= ~GravatarResolvUrlJobPrivate::Libravatar;
0184     }
0185 }
0186 
0187 bool GravatarResolvUrlJob::useDefaultPixmap() const
0188 {
0189     return d->mUseDefaultPixmap;
0190 }
0191 
0192 void GravatarResolvUrlJob::setUseDefaultPixmap(bool useDefaultPixmap)
0193 {
0194     d->mUseDefaultPixmap = useDefaultPixmap;
0195 }
0196 
0197 int GravatarResolvUrlJob::size() const
0198 {
0199     return d->mSize;
0200 }
0201 
0202 QPixmap GravatarResolvUrlJob::pixmap() const
0203 {
0204     return d->mPixmap;
0205 }
0206 
0207 void GravatarResolvUrlJob::setSize(int size)
0208 {
0209     if (size <= 0) {
0210         size = 80;
0211     } else if (size > 2048) {
0212         size = 2048;
0213     }
0214     d->mSize = size;
0215 }
0216 
0217 Hash GravatarResolvUrlJob::calculatedHash() const
0218 {
0219     return d->mCalculatedHash;
0220 }
0221 
0222 QUrl GravatarResolvUrlJob::createUrl(bool useLibravatar)
0223 {
0224     QUrl url;
0225     d->mCalculatedHash = Hash();
0226     if (!canStart()) {
0227         return url;
0228     }
0229     QUrlQuery query;
0230     if (!d->mUseDefaultPixmap) {
0231         // Add ?d=404
0232         query.addQueryItem(QStringLiteral("d"), QStringLiteral("404"));
0233     }
0234     if (d->mSize != 80) {
0235         query.addQueryItem(QStringLiteral("s"), QString::number(d->mSize));
0236     }
0237     url.setScheme(QStringLiteral("https"));
0238     if (useLibravatar) {
0239         url.setHost(QStringLiteral("seccdn.libravatar.org"));
0240     } else {
0241         url.setHost(QStringLiteral("secure.gravatar.com"));
0242     }
0243     d->mCalculatedHash = calculateHash();
0244     url.setPath(QLatin1StringView("/avatar/") + d->mCalculatedHash.hexString());
0245     url.setQuery(query);
0246     return url;
0247 }
0248 
0249 bool GravatarResolvUrlJob::cacheLookup(const Hash &hash)
0250 {
0251     bool haveStoredPixmap = false;
0252     const QPixmap pix = GravatarCache::self()->loadGravatarPixmap(hash, haveStoredPixmap);
0253     if (haveStoredPixmap && !pix.isNull()) { // we know a Gravatar for this hash
0254         d->mPixmap = pix;
0255         d->mHasGravatar = true;
0256         Q_EMIT finished(this);
0257         deleteLater();
0258         return true;
0259     }
0260     return haveStoredPixmap;
0261 }
0262 
0263 #include "moc_gravatarresolvurljob.cpp"