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"