File indexing completed on 2024-04-21 03:55:03

0001 /*
0002     SPDX-FileCopyrightText: 2008 Roland Harnau <tau@gmx.eu>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "hostinfo.h"
0008 
0009 #include <QCache>
0010 #include <QFutureWatcher>
0011 #include <QHash>
0012 #include <QHostInfo>
0013 #include <QList>
0014 #include <QMetaType>
0015 #include <QPair>
0016 #include <QSemaphore>
0017 #include <QThread>
0018 #include <QTime>
0019 #include <QTimer>
0020 #include <QtConcurrentRun>
0021 
0022 #ifdef Q_OS_UNIX
0023 #include <QFileInfo>
0024 #include <arpa/nameser.h>
0025 #include <netinet/in.h>
0026 #include <resolv.h> // for _PATH_RESCONF
0027 #ifndef _PATH_RESCONF
0028 #define _PATH_RESCONF "/etc/resolv.conf"
0029 #endif
0030 #endif
0031 
0032 static constexpr int TTL = 300;
0033 
0034 namespace KIO
0035 {
0036 class HostInfoAgentPrivate : public QObject
0037 {
0038     Q_OBJECT
0039 
0040     class Query;
0041 
0042 public:
0043     explicit HostInfoAgentPrivate(int cacheSize = 100);
0044     ~HostInfoAgentPrivate() override
0045     {
0046     }
0047     void lookupHost(const QString &hostName, QObject *receiver, const char *member);
0048     QHostInfo lookupCachedHostInfoFor(const QString &hostName);
0049     void cacheLookup(const QHostInfo &);
0050 private Q_SLOTS:
0051     void queryFinished(const QHostInfo &info, Query *sender);
0052 
0053 private:
0054     class Result;
0055 
0056     QHash<QString, Query *> openQueries;
0057     struct HostCacheInfo {
0058         QHostInfo hostInfo;
0059         QTime time;
0060     };
0061     QCache<QString, HostCacheInfo> dnsCache;
0062     QDateTime resolvConfMTime;
0063 };
0064 
0065 class HostInfoAgentPrivate::Result : public QObject
0066 {
0067     Q_OBJECT
0068 Q_SIGNALS:
0069     void result(const QHostInfo &);
0070 
0071 private:
0072     friend class HostInfoAgentPrivate;
0073 };
0074 
0075 class HostInfoAgentPrivate::Query : public QObject
0076 {
0077     Q_OBJECT
0078 public:
0079     Query()
0080         : m_watcher()
0081         , m_hostName()
0082     {
0083         connect(&m_watcher, &QFutureWatcher<QHostInfo>::finished, this, &Query::relayFinished);
0084     }
0085     void start(const QString &hostName)
0086     {
0087         m_hostName = hostName;
0088         QFuture<QHostInfo> future = QtConcurrent::run(&QHostInfo::fromName, hostName);
0089         m_watcher.setFuture(future);
0090     }
0091     QString hostName() const
0092     {
0093         return m_hostName;
0094     }
0095 Q_SIGNALS:
0096     void result(const QHostInfo &);
0097 private Q_SLOTS:
0098     void relayFinished()
0099     {
0100         Q_EMIT result(m_watcher.result());
0101     }
0102 
0103 private:
0104     QFutureWatcher<QHostInfo> m_watcher;
0105     QString m_hostName;
0106 };
0107 
0108 class NameLookupThreadRequest
0109 {
0110 public:
0111     NameLookupThreadRequest(const QString &hostName)
0112         : m_hostName(hostName)
0113     {
0114     }
0115 
0116     QSemaphore *semaphore()
0117     {
0118         return &m_semaphore;
0119     }
0120 
0121     QHostInfo result() const
0122     {
0123         return m_hostInfo;
0124     }
0125 
0126     void setResult(const QHostInfo &hostInfo)
0127     {
0128         m_hostInfo = hostInfo;
0129     }
0130 
0131     QString hostName() const
0132     {
0133         return m_hostName;
0134     }
0135 
0136     int lookupId() const
0137     {
0138         return m_lookupId;
0139     }
0140 
0141     void setLookupId(int id)
0142     {
0143         m_lookupId = id;
0144     }
0145 
0146 private:
0147     Q_DISABLE_COPY(NameLookupThreadRequest)
0148     QString m_hostName;
0149     QSemaphore m_semaphore;
0150     QHostInfo m_hostInfo;
0151     int m_lookupId;
0152 };
0153 }
0154 
0155 Q_DECLARE_METATYPE(std::shared_ptr<KIO::NameLookupThreadRequest>)
0156 
0157 namespace KIO
0158 {
0159 class NameLookUpThreadWorker : public QObject
0160 {
0161     Q_OBJECT
0162 public Q_SLOTS:
0163     void lookupHost(const std::shared_ptr<KIO::NameLookupThreadRequest> &request)
0164     {
0165         const QString hostName = request->hostName();
0166         const int lookupId = QHostInfo::lookupHost(hostName, this, SLOT(lookupFinished(QHostInfo)));
0167         request->setLookupId(lookupId);
0168         m_lookups.insert(lookupId, request);
0169     }
0170 
0171     void abortLookup(const std::shared_ptr<KIO::NameLookupThreadRequest> &request)
0172     {
0173         QHostInfo::abortHostLookup(request->lookupId());
0174         m_lookups.remove(request->lookupId());
0175     }
0176 
0177     void lookupFinished(const QHostInfo &hostInfo)
0178     {
0179         auto it = m_lookups.find(hostInfo.lookupId());
0180         if (it != m_lookups.end()) {
0181             (*it)->setResult(hostInfo);
0182             (*it)->semaphore()->release();
0183             m_lookups.erase(it);
0184         }
0185     }
0186 
0187 private:
0188     QMap<int, std::shared_ptr<NameLookupThreadRequest>> m_lookups;
0189 };
0190 
0191 class NameLookUpThread : public QThread
0192 {
0193     Q_OBJECT
0194 public:
0195     NameLookUpThread()
0196         : m_worker(nullptr)
0197     {
0198         qRegisterMetaType<std::shared_ptr<NameLookupThreadRequest>>();
0199         start();
0200     }
0201 
0202     ~NameLookUpThread() override
0203     {
0204         quit();
0205         wait();
0206     }
0207 
0208     NameLookUpThreadWorker *worker()
0209     {
0210         return m_worker;
0211     }
0212 
0213     QSemaphore *semaphore()
0214     {
0215         return &m_semaphore;
0216     }
0217 
0218     void run() override
0219     {
0220         NameLookUpThreadWorker worker;
0221         m_worker = &worker;
0222         m_semaphore.release();
0223         exec();
0224     }
0225 
0226 private:
0227     NameLookUpThreadWorker *m_worker;
0228     QSemaphore m_semaphore;
0229 };
0230 }
0231 
0232 using namespace KIO;
0233 
0234 Q_GLOBAL_STATIC(HostInfoAgentPrivate, hostInfoAgentPrivate)
0235 Q_GLOBAL_STATIC(NameLookUpThread, nameLookUpThread)
0236 
0237 void HostInfo::lookupHost(const QString &hostName, QObject *receiver, const char *member)
0238 {
0239     hostInfoAgentPrivate()->lookupHost(hostName, receiver, member);
0240 }
0241 
0242 QHostInfo HostInfo::lookupHost(const QString &hostName, unsigned long timeout)
0243 {
0244     // Do not perform a reverse lookup here...
0245     QHostAddress address(hostName);
0246     QHostInfo hostInfo;
0247     if (!address.isNull()) {
0248         QList<QHostAddress> addressList;
0249         addressList << address;
0250         hostInfo.setAddresses(addressList);
0251         return hostInfo;
0252     }
0253 
0254     // Look up the name in the KIO DNS cache...
0255     hostInfo = HostInfo::lookupCachedHostInfoFor(hostName);
0256     if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) {
0257         return hostInfo;
0258     }
0259 
0260     // Failing all of the above, do the lookup...
0261     std::shared_ptr<NameLookupThreadRequest> request = std::make_shared<NameLookupThreadRequest>(hostName);
0262     nameLookUpThread()->semaphore()->acquire();
0263     nameLookUpThread()->semaphore()->release();
0264     NameLookUpThreadWorker *worker = nameLookUpThread()->worker();
0265     auto lookupFunc = [worker, request]() {
0266         worker->lookupHost(request);
0267     };
0268     QMetaObject::invokeMethod(worker, lookupFunc, Qt::QueuedConnection);
0269     if (request->semaphore()->tryAcquire(1, timeout)) {
0270         hostInfo = request->result();
0271         if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) {
0272             HostInfo::cacheLookup(hostInfo); // cache the look up...
0273         }
0274     } else {
0275         auto abortFunc = [worker, request]() {
0276             worker->abortLookup(request);
0277         };
0278         QMetaObject::invokeMethod(worker, abortFunc, Qt::QueuedConnection);
0279     }
0280 
0281     // qDebug() << "Name look up succeeded for" << hostName;
0282     return hostInfo;
0283 }
0284 
0285 QHostInfo HostInfo::lookupCachedHostInfoFor(const QString &hostName)
0286 {
0287     return hostInfoAgentPrivate()->lookupCachedHostInfoFor(hostName);
0288 }
0289 
0290 void HostInfo::cacheLookup(const QHostInfo &info)
0291 {
0292     hostInfoAgentPrivate()->cacheLookup(info);
0293 }
0294 
0295 HostInfoAgentPrivate::HostInfoAgentPrivate(int cacheSize)
0296     : openQueries()
0297     , dnsCache(cacheSize)
0298 {
0299     qRegisterMetaType<QHostInfo>();
0300 }
0301 
0302 void HostInfoAgentPrivate::lookupHost(const QString &hostName, QObject *receiver, const char *member)
0303 {
0304 #ifdef _PATH_RESCONF
0305     QFileInfo resolvConf(QFile::decodeName(_PATH_RESCONF));
0306     QDateTime currentMTime = resolvConf.lastModified();
0307     if (resolvConf.exists() && currentMTime != resolvConfMTime) {
0308         // /etc/resolv.conf has been modified
0309         // clear our cache
0310         resolvConfMTime = currentMTime;
0311         dnsCache.clear();
0312     }
0313 #endif
0314 
0315     if (HostCacheInfo *info = dnsCache.object(hostName)) {
0316         if (QTime::currentTime() <= info->time.addSecs(TTL)) {
0317             Result result;
0318             if (receiver) {
0319                 QObject::connect(&result, SIGNAL(result(QHostInfo)), receiver, member);
0320                 Q_EMIT result.result(info->hostInfo);
0321             }
0322             return;
0323         }
0324         dnsCache.remove(hostName);
0325     }
0326 
0327     if (Query *query = openQueries.value(hostName)) {
0328         if (receiver) {
0329             connect(query, SIGNAL(result(QHostInfo)), receiver, member);
0330         }
0331         return;
0332     }
0333 
0334     Query *query = new Query();
0335     openQueries.insert(hostName, query);
0336     connect(query, &Query::result, this, [this, query](const QHostInfo &info) {
0337         queryFinished(info, query);
0338     });
0339     if (receiver) {
0340         connect(query, SIGNAL(result(QHostInfo)), receiver, member);
0341     }
0342     query->start(hostName);
0343 }
0344 
0345 QHostInfo HostInfoAgentPrivate::lookupCachedHostInfoFor(const QString &hostName)
0346 {
0347     HostCacheInfo *info = dnsCache.object(hostName);
0348     if (info && info->time.addSecs(TTL) >= QTime::currentTime()) {
0349         return info->hostInfo;
0350     }
0351 
0352     // not found in dnsCache
0353     QHostInfo hostInfo;
0354     hostInfo.setError(QHostInfo::HostNotFound);
0355     return hostInfo;
0356 }
0357 
0358 void HostInfoAgentPrivate::cacheLookup(const QHostInfo &info)
0359 {
0360     if (info.hostName().isEmpty()) {
0361         return;
0362     }
0363 
0364     if (info.error() != QHostInfo::NoError) {
0365         return;
0366     }
0367 
0368     dnsCache.insert(info.hostName(), new HostCacheInfo{info, QTime::currentTime()});
0369 }
0370 
0371 void HostInfoAgentPrivate::queryFinished(const QHostInfo &info, Query *sender)
0372 {
0373     const auto host = sender->hostName();
0374     openQueries.remove(host);
0375     if (info.error() == QHostInfo::NoError) {
0376         dnsCache.insert(host, new HostCacheInfo{info, QTime::currentTime()});
0377     }
0378     sender->deleteLater();
0379 }
0380 
0381 #include "hostinfo.moc"