1/*
2 SPDX-FileCopyrightText: 2008 Roland Harnau <tau@gmx.eu>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "hostinfo.h"
8
9#include <QCache>
10#include <QFutureWatcher>
11#include <QHash>
12#include <QHostInfo>
13#include <QList>
14#include <QMetaType>
15#include <QPair>
16#include <QSemaphore>
17#include <QThread>
18#include <QTime>
19#include <QTimer>
20#include <QtConcurrentRun>
21
22#ifdef Q_OS_UNIX
23#include <QFileInfo>
24#include <arpa/nameser.h>
25#include <netinet/in.h>
26#include <resolv.h> // for _PATH_RESCONF
27#ifndef _PATH_RESCONF
28#define _PATH_RESCONF "/etc/resolv.conf"
29#endif
30#endif
31
32static constexpr int TTL = 300;
33
34namespace KIO
35{
36class HostInfoAgentPrivate : public QObject
37{
38 Q_OBJECT
39
40 class Query;
41
42public:
43 explicit HostInfoAgentPrivate(int cacheSize = 100);
44 ~HostInfoAgentPrivate() override
45 {
46 }
47 void lookupHost(const QString &hostName, QObject *receiver, const char *member);
48 QHostInfo lookupCachedHostInfoFor(const QString &hostName);
49 void cacheLookup(const QHostInfo &);
50private Q_SLOTS:
51 void queryFinished(const QHostInfo &info, Query *sender);
52
53private:
54 class Result;
55
56 QHash<QString, Query *> openQueries;
57 struct HostCacheInfo {
58 QHostInfo hostInfo;
59 QTime time;
60 };
61 QCache<QString, HostCacheInfo> dnsCache;
62 QDateTime resolvConfMTime;
63};
64
65class HostInfoAgentPrivate::Result : public QObject
66{
67 Q_OBJECT
68Q_SIGNALS:
69 void result(const QHostInfo &);
70
71private:
72 friend class HostInfoAgentPrivate;
73};
74
75class HostInfoAgentPrivate::Query : public QObject
76{
77 Q_OBJECT
78public:
79 Query()
80 : m_watcher()
81 , m_hostName()
82 {
83 connect(sender: &m_watcher, signal: &QFutureWatcher<QHostInfo>::finished, context: this, slot: &Query::relayFinished);
84 }
85 void start(const QString &hostName)
86 {
87 m_hostName = hostName;
88 QFuture<QHostInfo> future = QtConcurrent::run(f: &QHostInfo::fromName, args: hostName);
89 m_watcher.setFuture(future);
90 }
91 QString hostName() const
92 {
93 return m_hostName;
94 }
95Q_SIGNALS:
96 void result(const QHostInfo &);
97private Q_SLOTS:
98 void relayFinished()
99 {
100 Q_EMIT result(m_watcher.result());
101 }
102
103private:
104 QFutureWatcher<QHostInfo> m_watcher;
105 QString m_hostName;
106};
107
108class NameLookupThreadRequest
109{
110public:
111 NameLookupThreadRequest(const QString &hostName)
112 : m_hostName(hostName)
113 {
114 }
115
116 QSemaphore *semaphore()
117 {
118 return &m_semaphore;
119 }
120
121 QHostInfo result() const
122 {
123 return m_hostInfo;
124 }
125
126 void setResult(const QHostInfo &hostInfo)
127 {
128 m_hostInfo = hostInfo;
129 }
130
131 QString hostName() const
132 {
133 return m_hostName;
134 }
135
136 int lookupId() const
137 {
138 return m_lookupId;
139 }
140
141 void setLookupId(int id)
142 {
143 m_lookupId = id;
144 }
145
146private:
147 Q_DISABLE_COPY(NameLookupThreadRequest)
148 QString m_hostName;
149 QSemaphore m_semaphore;
150 QHostInfo m_hostInfo;
151 int m_lookupId;
152};
153}
154
155Q_DECLARE_METATYPE(std::shared_ptr<KIO::NameLookupThreadRequest>)
156
157namespace KIO
158{
159class NameLookUpThreadWorker : public QObject
160{
161 Q_OBJECT
162public Q_SLOTS:
163 void lookupHost(const std::shared_ptr<KIO::NameLookupThreadRequest> &request)
164 {
165 const QString hostName = request->hostName();
166 const int lookupId = QHostInfo::lookupHost(name: hostName, receiver: this, SLOT(lookupFinished(QHostInfo)));
167 request->setLookupId(lookupId);
168 m_lookups.insert(key: lookupId, value: request);
169 }
170
171 void abortLookup(const std::shared_ptr<KIO::NameLookupThreadRequest> &request)
172 {
173 QHostInfo::abortHostLookup(lookupId: request->lookupId());
174 m_lookups.remove(key: request->lookupId());
175 }
176
177 void lookupFinished(const QHostInfo &hostInfo)
178 {
179 auto it = m_lookups.find(key: hostInfo.lookupId());
180 if (it != m_lookups.end()) {
181 (*it)->setResult(hostInfo);
182 (*it)->semaphore()->release();
183 m_lookups.erase(it);
184 }
185 }
186
187private:
188 QMap<int, std::shared_ptr<NameLookupThreadRequest>> m_lookups;
189};
190
191class NameLookUpThread : public QThread
192{
193 Q_OBJECT
194public:
195 NameLookUpThread()
196 : m_worker(nullptr)
197 {
198 qRegisterMetaType<std::shared_ptr<NameLookupThreadRequest>>();
199 start();
200 }
201
202 ~NameLookUpThread() override
203 {
204 quit();
205 wait();
206 }
207
208 NameLookUpThreadWorker *worker()
209 {
210 return m_worker;
211 }
212
213 QSemaphore *semaphore()
214 {
215 return &m_semaphore;
216 }
217
218 void run() override
219 {
220 NameLookUpThreadWorker worker;
221 m_worker = &worker;
222 m_semaphore.release();
223 exec();
224 }
225
226private:
227 NameLookUpThreadWorker *m_worker;
228 QSemaphore m_semaphore;
229};
230}
231
232using namespace KIO;
233
234Q_GLOBAL_STATIC(HostInfoAgentPrivate, hostInfoAgentPrivate)
235Q_GLOBAL_STATIC(NameLookUpThread, nameLookUpThread)
236
237void HostInfo::lookupHost(const QString &hostName, QObject *receiver, const char *member)
238{
239 hostInfoAgentPrivate()->lookupHost(hostName, receiver, member);
240}
241
242QHostInfo HostInfo::lookupHost(const QString &hostName, unsigned long timeout)
243{
244 // Do not perform a reverse lookup here...
245 QHostAddress address(hostName);
246 QHostInfo hostInfo;
247 if (!address.isNull()) {
248 QList<QHostAddress> addressList;
249 addressList << address;
250 hostInfo.setAddresses(addressList);
251 return hostInfo;
252 }
253
254 // Look up the name in the KIO DNS cache...
255 hostInfo = HostInfo::lookupCachedHostInfoFor(hostName);
256 if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) {
257 return hostInfo;
258 }
259
260 // Failing all of the above, do the lookup...
261 std::shared_ptr<NameLookupThreadRequest> request = std::make_shared<NameLookupThreadRequest>(args: hostName);
262 nameLookUpThread()->semaphore()->acquire();
263 nameLookUpThread()->semaphore()->release();
264 NameLookUpThreadWorker *worker = nameLookUpThread()->worker();
265 auto lookupFunc = [worker, request]() {
266 worker->lookupHost(request);
267 };
268 QMetaObject::invokeMethod(object: worker, function&: lookupFunc, type: Qt::QueuedConnection);
269 if (request->semaphore()->tryAcquire(n: 1, timeout)) {
270 hostInfo = request->result();
271 if (!hostInfo.hostName().isEmpty() && hostInfo.error() == QHostInfo::NoError) {
272 HostInfo::cacheLookup(info: hostInfo); // cache the look up...
273 }
274 } else {
275 auto abortFunc = [worker, request]() {
276 worker->abortLookup(request);
277 };
278 QMetaObject::invokeMethod(object: worker, function&: abortFunc, type: Qt::QueuedConnection);
279 }
280
281 // qDebug() << "Name look up succeeded for" << hostName;
282 return hostInfo;
283}
284
285QHostInfo HostInfo::lookupCachedHostInfoFor(const QString &hostName)
286{
287 return hostInfoAgentPrivate()->lookupCachedHostInfoFor(hostName);
288}
289
290void HostInfo::cacheLookup(const QHostInfo &info)
291{
292 hostInfoAgentPrivate()->cacheLookup(info);
293}
294
295HostInfoAgentPrivate::HostInfoAgentPrivate(int cacheSize)
296 : openQueries()
297 , dnsCache(cacheSize)
298{
299 qRegisterMetaType<QHostInfo>();
300}
301
302void HostInfoAgentPrivate::lookupHost(const QString &hostName, QObject *receiver, const char *member)
303{
304#ifdef _PATH_RESCONF
305 QFileInfo resolvConf(QFile::decodeName(_PATH_RESCONF));
306 QDateTime currentMTime = resolvConf.lastModified();
307 if (resolvConf.exists() && currentMTime != resolvConfMTime) {
308 // /etc/resolv.conf has been modified
309 // clear our cache
310 resolvConfMTime = currentMTime;
311 dnsCache.clear();
312 }
313#endif
314
315 if (HostCacheInfo *info = dnsCache.object(key: hostName)) {
316 if (QTime::currentTime() <= info->time.addSecs(secs: TTL)) {
317 Result result;
318 if (receiver) {
319 QObject::connect(sender: &result, SIGNAL(result(QHostInfo)), receiver, member);
320 Q_EMIT result.result(info->hostInfo);
321 }
322 return;
323 }
324 dnsCache.remove(key: hostName);
325 }
326
327 if (Query *query = openQueries.value(key: hostName)) {
328 if (receiver) {
329 connect(sender: query, SIGNAL(result(QHostInfo)), receiver, member);
330 }
331 return;
332 }
333
334 Query *query = new Query();
335 openQueries.insert(key: hostName, value: query);
336 connect(sender: query, signal: &Query::result, context: this, slot: [this, query](const QHostInfo &info) {
337 queryFinished(info, sender: query);
338 });
339 if (receiver) {
340 connect(sender: query, SIGNAL(result(QHostInfo)), receiver, member);
341 }
342 query->start(hostName);
343}
344
345QHostInfo HostInfoAgentPrivate::lookupCachedHostInfoFor(const QString &hostName)
346{
347 HostCacheInfo *info = dnsCache.object(key: hostName);
348 if (info && info->time.addSecs(secs: TTL) >= QTime::currentTime()) {
349 return info->hostInfo;
350 }
351
352 // not found in dnsCache
353 QHostInfo hostInfo;
354 hostInfo.setError(QHostInfo::HostNotFound);
355 return hostInfo;
356}
357
358void HostInfoAgentPrivate::cacheLookup(const QHostInfo &info)
359{
360 if (info.hostName().isEmpty()) {
361 return;
362 }
363
364 if (info.error() != QHostInfo::NoError) {
365 return;
366 }
367
368 dnsCache.insert(key: info.hostName(), object: new HostCacheInfo{.hostInfo: info, .time: QTime::currentTime()});
369}
370
371void HostInfoAgentPrivate::queryFinished(const QHostInfo &info, Query *sender)
372{
373 const auto host = sender->hostName();
374 openQueries.remove(key: host);
375 if (info.error() == QHostInfo::NoError) {
376 dnsCache.insert(key: host, object: new HostCacheInfo{.hostInfo: info, .time: QTime::currentTime()});
377 }
378 sender->deleteLater();
379}
380
381#include "hostinfo.moc"
382

source code of kio/src/core/hostinfo.cpp