File indexing completed on 2024-05-05 16:13:11

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