1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qglobal.h>
5
6#include <QtNetwork/private/qtnetworkglobal_p.h>
7
8#if QT_CONFIG(topleveldomain)
9
10#include "QtCore/qfile.h"
11#include "QtCore/qloggingcategory.h"
12#include "QtCore/qstandardpaths.h"
13#include "QtCore/qstring.h"
14
15#if !QT_CONFIG(publicsuffix_qt) && !QT_CONFIG(publicsuffix_system)
16# error Enable at least one feature: publicsuffix-qt, publicsuffix-system
17#endif
18
19#if QT_CONFIG(publicsuffix_qt)
20# include "psl_data.cpp"
21#endif
22
23// Defined in src/3rdparty/libpsl/src/lookup_string_in_fixed_set.c
24extern "C" int LookupStringInFixedSet(const unsigned char *graph, std::size_t length,
25 const char *key, std::size_t key_length);
26
27QT_BEGIN_NAMESPACE
28
29using namespace Qt::StringLiterals;
30
31Q_LOGGING_CATEGORY(lcTld, "qt.network.tld")
32
33static constexpr int PSL_NOT_FOUND = -1;
34static constexpr int PSL_FLAG_EXCEPTION = 1 << 0;
35static constexpr int PSL_FLAG_WILDCARD = 1 << 1;
36
37class QPublicSuffixDatabase final
38{
39public:
40#if QT_CONFIG(publicsuffix_system)
41 QPublicSuffixDatabase();
42#endif // QT_CONFIG(publicsuffix_system)
43
44 int lookupDomain(QByteArrayView domain) const;
45
46private:
47 QByteArrayView m_data
48#if QT_CONFIG(publicsuffix_qt)
49 {
50 kDafsa, sizeof(kDafsa)
51 }
52#endif // QT_CONFIG(publicsuffix_qt)
53 ;
54
55#if QT_CONFIG(publicsuffix_system)
56 std::unique_ptr<QFile> m_dev;
57 QByteArray m_storage;
58 bool loadFile(const QString &fileName);
59#endif // QT_CONFIG(publicsuffix_system)
60};
61
62int QPublicSuffixDatabase::lookupDomain(QByteArrayView domain) const
63{
64 return LookupStringInFixedSet(graph: reinterpret_cast<const unsigned char *>(m_data.constData()),
65 length: m_data.size(), key: domain.data(), key_length: domain.size());
66}
67
68#if QT_CONFIG(publicsuffix_system)
69
70static QStringList locatePublicSuffixFiles()
71{
72 return QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation,
73 fileName: u"publicsuffix/public_suffix_list.dafsa"_s);
74}
75
76QPublicSuffixDatabase::QPublicSuffixDatabase()
77{
78 for (auto &&fileName : locatePublicSuffixFiles()) {
79 if (loadFile(fileName))
80 return;
81 }
82
83#if QT_CONFIG(publicsuffix_qt)
84 qCDebug(lcTld, "Using builtin publicsuffix list");
85#else
86 qCWarning(lcTld, "No usable publicsuffix file found");
87#endif
88}
89
90bool QPublicSuffixDatabase::loadFile(const QString &fileName)
91{
92 static const QByteArrayView DafsaFileHeader = ".DAFSA@PSL_0 \n";
93
94 qCDebug(lcTld, "Loading publicsuffix file: %s", qUtf8Printable(fileName));
95
96 auto systemFile = std::make_unique<QFile>(args: fileName);
97
98 if (!systemFile->open(flags: QIODevice::ReadOnly)) {
99 qCDebug(lcTld, "Failed to open publicsuffix file: %s",
100 qUtf8Printable(systemFile->errorString()));
101 return false;
102 }
103
104 auto fileSize = systemFile->size();
105 // Check if there is enough data for header, version byte and some data
106 if (fileSize < DafsaFileHeader.size() + 2) {
107 qCWarning(lcTld, "publicsuffix file is too small: %zu", std::size_t(fileSize));
108 return false;
109 }
110
111 auto header = systemFile->read(maxlen: DafsaFileHeader.size());
112 if (header != DafsaFileHeader) {
113 qCWarning(lcTld, "Invalid publicsuffix file header: %s", header.toHex().constData());
114 return false;
115 }
116
117 // Check if the file is UTF-8 compatible
118 if (!systemFile->seek(offset: fileSize - 1)) {
119 qCWarning(lcTld, "Failed to seek to the end of file: %s",
120 qUtf8Printable(systemFile->errorString()));
121 return false;
122 }
123
124 char version;
125 if (systemFile->read(data: &version, maxlen: 1) != 1) {
126 qCWarning(lcTld, "Failed to read publicsuffix version");
127 return false;
128 }
129
130 if (version != 0x01) {
131 qCWarning(lcTld, "Unsupported publicsuffix version: %d", int(version));
132 return false;
133 }
134
135 const auto dataSize = fileSize - DafsaFileHeader.size() - 1;
136 // Try to map the file first
137 auto mappedData = systemFile->map(offset: DafsaFileHeader.size(), size: dataSize);
138 if (mappedData) {
139 qCDebug(lcTld, "Using mapped system publicsuffix data");
140 systemFile->close();
141 m_data = QByteArrayView(mappedData, dataSize);
142 m_dev = std::move(systemFile);
143 return true;
144 }
145
146 qCDebug(lcTld, "Failed to map publicsuffix file: %s",
147 qUtf8Printable(systemFile->errorString()));
148
149 systemFile->seek(offset: DafsaFileHeader.size());
150 m_storage = systemFile->read(maxlen: dataSize);
151 if (m_storage.size() != dataSize) {
152 qCWarning(lcTld, "Failed to read publicsuffix file");
153 m_storage.clear();
154 return false;
155 }
156
157 qCDebug(lcTld, "Using system publicsuffix data");
158 m_data = m_storage;
159
160 return true;
161}
162
163Q_GLOBAL_STATIC(QPublicSuffixDatabase, publicSuffix);
164
165#else
166
167static const QPublicSuffixDatabase m_publicSuffix;
168
169#endif // QT_CONFIG(publicsuffix_system)
170
171/*!
172 \internal
173
174 Return true if \a domain is a top-level-domain per Qt's copy of the Mozilla public suffix list.
175
176 The \a domain must be in lower-case format (as per QString::toLower()).
177*/
178
179Q_NETWORK_EXPORT bool qIsEffectiveTLD(QStringView domain)
180{
181 // for domain 'foo.bar.com':
182 // 1. return false if TLD table contains '!foo.bar.com'
183 // 2. return true if TLD table contains 'foo.bar.com'
184 // 3. return true if the table contains '*.bar.com'
185
186 QByteArray decodedDomain = domain.toUtf8();
187 QByteArrayView domainView(decodedDomain);
188
189#if QT_CONFIG(publicsuffix_system)
190 if (publicSuffix.isDestroyed())
191 return false;
192#else
193 auto publicSuffix = &m_publicSuffix;
194#endif // QT_CONFIG(publicsuffix_system)
195
196 auto ret = publicSuffix->lookupDomain(domain: domainView);
197 if (ret != PSL_NOT_FOUND) {
198 if (ret & PSL_FLAG_EXCEPTION) // 1
199 return false;
200 if ((ret & PSL_FLAG_WILDCARD) == 0) // 2
201 return true;
202 }
203
204 const auto dot = domainView.indexOf(ch: '.');
205 if (dot < 0) // Actual TLD: may be effective if the subject of a wildcard rule:
206 return ret != PSL_NOT_FOUND;
207 ret = publicSuffix->lookupDomain(domain: domainView.sliced(pos: dot + 1)); // 3
208 if (ret == PSL_NOT_FOUND)
209 return false;
210 return (ret & PSL_FLAG_WILDCARD) != 0;
211}
212
213QT_END_NAMESPACE
214
215#endif // QT_CONFIG(topleveldomain)
216

source code of qtbase/src/network/kernel/qtldurl.cpp