cutelyst 4.3.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
validatordomain.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2018-2023 Matthias Fehring <mf@huessenbergnetz.de>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5
6#include "validatordomain_p.h"
7
8#include <QDnsLookup>
9#include <QEventLoop>
10#include <QStringList>
11#include <QTimer>
12#include <QUrl>
13
14using namespace Cutelyst;
15
17 bool checkDNS,
18 const ValidatorMessages &messages,
19 const QString &defValKey)
20 : ValidatorRule(*new ValidatorDomainPrivate(field, checkDNS, messages, defValKey))
21{
22}
23
25
27 bool checkDNS,
29 QString *extractedValue)
30{
31 bool valid = true;
32
33 Diagnose diag = Valid;
34
35 QString _v = value;
36 bool hasRootDot = false;
37 if (_v.endsWith(u'.')) {
38 hasRootDot = true;
39 _v.chop(1);
40 }
41
42 // convert to lower case puny code
44
45 // split up the utf8 string into parts to get the non puny code TLD
46 const QStringList nonAceParts = _v.split(QLatin1Char('.'));
47 if (!nonAceParts.empty()) {
48 const QString tld = nonAceParts.last();
49 if (!tld.isEmpty()) {
50 // there are no TLDs with digits inside, but IDN TLDs can
51 // have digits in their puny code representation, so we have
52 // to check at first if the IDN TLD contains digits before
53 // checking the ACE puny code
54 for (const QChar &ch : tld) {
55 const ushort &uc = ch.unicode();
56 if (((uc >= ValidatorRulePrivate::ascii_0) &&
57 (uc <= ValidatorRulePrivate::ascii_9)) ||
58 (uc == ValidatorRulePrivate::ascii_dash)) {
59 diag = InvalidTLD;
60 valid = false;
61 break;
62 }
63 }
64
65 if (valid) {
66 if (!v.isEmpty()) {
67 // maximum length of the name in the DNS is 253 without the last dot
68 if (v.length() <= ValidatorDomainPrivate::maxDnsNameWithLastDot) {
69 const QStringList parts = v.split(QLatin1Char('.'), Qt::KeepEmptyParts);
70 // there has to be more than only the TLD
71 if (parts.size() > 1) {
72 // the TLD can not have only 1 char
73 if (parts.last().length() > 1) {
74 for (int i = 0; i < parts.size(); ++i) {
75 if (valid) {
76 const QString &part = parts.at(i);
77 if (!part.isEmpty()) {
78 // labels/parts can have a maximum length of 63 chars
79 if (part.length() <=
80 ValidatorDomainPrivate::maxDnsLabelLength) {
81 bool isTld = (i == (parts.size() - 1));
82 bool isPunyCode = part.startsWith(u"xn--");
83 for (int j = 0; j < part.size(); ++j) {
84 const ushort &uc = part.at(j).unicode();
85 const bool isDigit =
86 ((uc >= ValidatorRulePrivate::ascii_0) &&
87 (uc <= ValidatorRulePrivate::ascii_9));
88 const bool isDash =
89 (uc == ValidatorRulePrivate::ascii_dash);
90 // no part/label can start with a digit or a
91 // dash
92 if ((j == 0) && (isDash || isDigit)) {
93 valid = false;
94 diag = isDash ? DashStart : DigitStart;
95 break;
96 }
97 // no part/label can end with a dash
98 if ((j == (part.size() - 1)) && isDash) {
99 valid = false;
100 diag = DashEnd;
101 break;
102 }
103 const bool isChar =
104 ((uc >= ValidatorRulePrivate::ascii_a) &&
105 (uc <= ValidatorRulePrivate::ascii_z));
106 if (!isTld) {
107 // if it is not the tld, it can have a-z 0-9
108 // and -
109 if (!(isDigit || isDash || isChar)) {
110 valid = false;
111 diag = InvalidChars;
112 break;
113 }
114 } else {
115 if (isPunyCode) {
116 if (!(isDigit || isDash || isChar)) {
117 valid = false;
118 diag = InvalidTLD;
119 break;
120 }
121 } else {
122 if (!isChar) {
123 valid = false;
124 diag = InvalidTLD;
125 break;
126 }
127 }
128 }
129 }
130 } else {
131 valid = false;
132 diag = LabelTooLong;
133 break;
134 }
135 } else {
136 valid = false;
137 diag = EmptyLabel;
138 break;
139 }
140 } else {
141 break;
142 }
143 }
144 } else {
145 valid = false;
146 diag = InvalidTLD;
147 }
148 } else {
149 valid = false;
150 diag = InvalidLabelCount;
151 }
152 } else {
153 valid = false;
154 diag = TooLong;
155 }
156 } else {
157 valid = false;
158 diag = EmptyLabel;
159 }
160 }
161 } else {
162 valid = false;
163 diag = EmptyLabel;
164 }
165 } else {
166 valid = false;
167 diag = EmptyLabel;
168 }
169
170 if (valid && checkDNS) {
171 QDnsLookup alookup(QDnsLookup::A, v);
172 QEventLoop aloop;
174 QTimer::singleShot(ValidatorDomainPrivate::dnsLookupTimeout, &alookup, &QDnsLookup::abort);
175 alookup.lookup();
176 aloop.exec();
177
178 if (((alookup.error() != QDnsLookup::NoError) &&
180 alookup.hostAddressRecords().empty()) {
181 QDnsLookup aaaaLookup(QDnsLookup::AAAA, v);
182 QEventLoop aaaaLoop;
183 QObject::connect(&aaaaLookup, &QDnsLookup::finished, &aaaaLoop, &QEventLoop::quit);
185 ValidatorDomainPrivate::dnsLookupTimeout, &aaaaLookup, &QDnsLookup::abort);
186 aaaaLookup.lookup();
187 aaaaLoop.exec();
188
189 if (((aaaaLookup.error() != QDnsLookup::NoError) &&
190 (aaaaLookup.error() != QDnsLookup::OperationCancelledError)) ||
191 aaaaLookup.hostAddressRecords().empty()) {
192 valid = false;
193 diag = MissingDNS;
194 } else if (aaaaLookup.error() == QDnsLookup::OperationCancelledError) {
195 valid = false;
196 diag = DNSTimeout;
197 }
198 } else if (alookup.error() == QDnsLookup::OperationCancelledError) {
199 valid = false;
200 diag = DNSTimeout;
201 }
202 }
203
204 if (diagnose) {
205 *diagnose = diag;
206 }
207
208 if (valid && extractedValue) {
209 if (hasRootDot) {
210 *extractedValue = v + QLatin1Char('.');
211 } else {
212 *extractedValue = v;
213 }
214 }
215
216 return valid;
217}
218
220{
221 if (label.isEmpty()) {
222 switch (diagnose) {
223 case MissingDNS:
224 //% "The domain name seems to be valid but could not be found in the "
225 //% "domain name system."
226 return c->qtTrId("cutelyst-valdomain-diag-missingdns");
227 case InvalidChars:
228 //% "The domain name contains characters that are not allowed."
229 return c->qtTrId("cutelyst-valdomain-diag-invalidchars");
230 case LabelTooLong:
231 //% "At least one of the sections separated by dots exceeds the maximum "
232 //% "allowed length of 63 characters. Note that internationalized domain "
233 //% "names with non-ASCII characters can be longer internally than they are "
234 //% "displayed."
235 return c->qtTrId("cutelyst-valdomain-diag-labeltoolong");
236 case TooLong:
237 //% "The full name of the domain must not be longer than 253 characters. Note that "
238 //% "internationalized domain names with non-ASCII character can be longer internally "
239 //% "than they are displayed."
240 return c->qtTrId("cutelyst-valdomain-diag-toolong");
242 //% "This is not a valid domain name because it has either no parts "
243 //% "(is empty) or only has a top level domain."
244 return c->qtTrId("cutelyst-valdomain-diag-invalidlabelcount");
245 case EmptyLabel:
246 //% "At least one of the sections separated by dots is empty. Check "
247 //% "whether you have entered two dots consecutively."
248 return c->qtTrId("cutelyst-valdomain-diag-emptylabel");
249 case InvalidTLD:
250 //% "The top level domain (last part) contains characters that are "
251 //% "not allowed, like digits and/or dashes."
252 return c->qtTrId("cutelyst-valdomain-diag-invalidtld");
253 case DashStart:
254 //% "Domain name sections are not allowed to start with a dash."
255 return c->qtTrId("cutelyst-valdomain-diag-dashstart");
256 case DashEnd:
257 //% "Domain name sections are not allowed to end with a dash."
258 return c->qtTrId("cutelyst-valdomain-diag-dashend");
259 case DigitStart:
260 //% "Domain name sections are not allowed to start with a digit."
261 return c->qtTrId("cutelyst-valdomain-diag-digitstart");
262 case Valid:
263 //% "The domain name is valid."
264 return c->qtTrId("cutelyst-valdomain-diag-valid");
265 case DNSTimeout:
266 //% "The DNS lookup was aborted because it took too long."
267 return c->qtTrId("cutelyst-valdomain-diag-dnstimeout");
268 default:
269 Q_ASSERT_X(false, "domain validation diagnose", "invalid diagnose");
270 return {};
271 }
272 } else {
273 switch (diagnose) {
274 case MissingDNS:
275 //% "The domain name in the “%1“ field seems to be valid but could "
276 //% "not be found in the domain name system."
277 return c->qtTrId("cutelyst-valdomain-diag-missingdns-label").arg(label);
278 case InvalidChars:
279 //% "The domain name in the “%1“ field contains characters that are not allowed."
280 return c->qtTrId("cutelyst-valdomain-diag-invalidchars-label").arg(label);
281 case LabelTooLong:
282 //% "The domain name in the “%1“ field is not valid because at least "
283 //% "one of the sections separated by dots exceeds the maximum "
284 //% "allowed length of 63 characters. Note that internationalized "
285 //% "domain names with non-ASCII characters can be longer internally "
286 //% "than they are displayed."
287 return c->qtTrId("cutelyst-valdomain-diag-labeltoolong-label").arg(label);
288 case TooLong:
289 //% "The full name of the domain in the “%1” field must not be longer "
290 //% "than 253 characters. Note that internationalized domain names "
291 //% "with non-ASCII characters can be longer internally than they are displayed."
292 return c->qtTrId("cutelyst-valdomain-diag-toolong-label").arg(label);
294 //% "The “%1” field does not contain a valid domain name because it "
295 //% "has either no parts (is empty) or only has a top level domain."
296 return c->qtTrId("cutelyst-valdomain-diag-invalidlabelcount-label").arg(label);
297 case EmptyLabel:
298 //% "The domain name in the “%1“ field is not valid because at least "
299 //% "one of the sections separated by dots is empty. Check whether "
300 //% "you have entered two dots consecutively."
301 return c->qtTrId("cutelyst-valdomain-diag-emptylabel-label").arg(label);
302 case InvalidTLD:
303 //% "The top level domain (last part) of the domain name in the “%1” field "
304 //% "contains characters that are not allowed, like digits and or dashes."
305 return c->qtTrId("cutelyst-valdomain-diag-invalidtld-label").arg(label);
306 case DashStart:
307 //% "The domain name in the “%1“ field is not valid because domain "
308 //% "name sections are not allowed to start with a dash."
309 return c->qtTrId("cutelyst-valdomain-diag-dashstart-label").arg(label);
310 case DashEnd:
311 //% "The domain name in the “%1“ field is not valid because domain "
312 //% "name sections are not allowed to end with a dash."
313 return c->qtTrId("cutelyst-valdomain-diag-dashend-label").arg(label);
314 case DigitStart:
315 //% "The domain name in the “%1“ field is not valid because domain "
316 //% "name sections are not allowed to start with a digit."
317 return c->qtTrId("cutelyst-valdomain-diag-digitstart-label").arg(label);
318 case Valid:
319 //% "The domain name in the “%1” field is valid."
320 return c->qtTrId("cutelyst-valdomain-diag-valid-label").arg(label);
321 case DNSTimeout:
322 //% "The DNS lookup for the domain name in the “%1” field was aborted "
323 //% "because it took too long."
324 return c->qtTrId("cutelyst-valdomain-diag-dnstimeout-label").arg(label);
325 default:
326 Q_ASSERT_X(false, "domain validation diagnose", "invalid diagnose");
327 return {};
328 }
329 }
330}
331
333{
334 ValidatorReturnType result;
335
336 const QString &v = value(params);
337
338 if (!v.isEmpty()) {
339 Q_D(const ValidatorDomain);
340 QString exVal;
341 Diagnose diag{Valid};
342 if (ValidatorDomain::validate(v, d->checkDNS, &diag, &exVal)) {
343 result.value.setValue(exVal);
344 } else {
345 result.errorMessage = validationError(c, diag);
346 if (C_VALIDATOR().isDebugEnabled()) {
347 switch (diag) {
348 case Valid:
349 break;
350 case MissingDNS:
351 qCDebug(C_VALIDATOR).noquote()
352 << debugString(c) << "Can not find valid DNS entry for" << v;
353 break;
354 case InvalidChars:
355 qCDebug(C_VALIDATOR).noquote()
356 << debugString(c)
357 << "The domain name contains characters that are not allowed";
358 break;
359 case LabelTooLong:
360 qCDebug(C_VALIDATOR).noquote()
361 << debugString(c)
362 << "At least on of the domain name labels exceeds the maximum" << "size of"
363 << ValidatorDomainPrivate::maxDnsLabelLength << "characters";
364 break;
365 case TooLong:
366 qCDebug(C_VALIDATOR).noquote()
367 << debugString(c) << "The domain name exceeds the maximum size of"
368 << ValidatorDomainPrivate::maxDnsNameWithLastDot << "characters";
369 break;
371 qCDebug(C_VALIDATOR).noquote()
372 << debugString(c) << "Invalid label count. Either no labels or only TLD";
373 break;
374 case EmptyLabel:
375 qCDebug(C_VALIDATOR).noquote()
376 << debugString(c) << "At least one of the domain name labels is empty";
377 break;
378 case InvalidTLD:
379 qCDebug(C_VALIDATOR).noquote()
380 << debugString(c)
381 << "The TLD label contains characters that are not allowed";
382 break;
383 case DashStart:
384 qCDebug(C_VALIDATOR).noquote()
385 << debugString(c) << "At least one label starts with a dash";
386 break;
387 case DashEnd:
388 qCDebug(C_VALIDATOR).noquote()
389 << debugString(c) << "At least one label ends with a dash";
390 break;
391 case DigitStart:
392 qCDebug(C_VALIDATOR).noquote()
393 << debugString(c) << "At least one label starts with a digit";
394 break;
395 case DNSTimeout:
396 qCDebug(C_VALIDATOR).noquote()
397 << debugString(c) << "The DNS lookup exceeds the timeout of"
398#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
399 << ValidatorDomainPrivate::dnsLookupTimeout;
400#else
401 << ValidatorDomainPrivate::dnsLookupTimeout.count() << "milliseconds";
402#endif
403 }
404 }
405 }
406 } else {
407 defaultValue(c, &result);
408 }
409
410 return result;
411}
412
414{
415 return ValidatorDomain::diagnoseString(c, errorData.value<Diagnose>(), label(c));
416}
417
418#include "moc_validatordomain.cpp"
The Cutelyst Context.
Definition context.h:42
QString qtTrId(const char *id, int n=-1) const
Definition context.h:656
Checks if the value of the input field contains a FQDN according to RFC 1035.
QString genericValidationError(Context *c, const QVariant &errorData=QVariant()) const override
Returns a generic error message if validation failed.
static QString diagnoseString(Context *c, Diagnose diagnose, const QString &label=QString())
ValidatorDomain(const QString &field, bool checkDNS=false, const ValidatorMessages &messages=ValidatorMessages(), const QString &defValKey=QString())
Constructs a new ValidatorDomain object with the given parameters.
Diagnose
Possible diagnose information for the checked domain.
Base class for all validator rules.
QString validationError(Context *c, const QVariant &errorData={}) const
QString label(Context *c) const
void defaultValue(Context *c, ValidatorReturnType *result) const
QString value(const ParamsMultiMap &params) const
QString debugString(Context *c) const
static bool validate(const QString &value, bool checkDNS, Diagnose *diagnose=nullptr, QString *extractedValue=nullptr)
Returns true if value is a valid fully qualified domain name.
The Cutelyst namespace holds all public Cutelyst API.
char16_t & unicode()
void abort()
void finished()
QList< QDnsHostAddressRecord > hostAddressRecords() const const
void lookup()
int exec(QEventLoop::ProcessEventsFlags flags)
void quit()
QList::const_reference at(qsizetype i) const const
bool empty() const const
T & last()
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
qsizetype count() const const
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
qsizetype length() const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
KeepEmptyParts
QByteArray toAce(const QString &domain, QUrl::AceProcessingOptions options)
void setValue(QVariant &&value)
T value() const const
Stores custom error messages and the input field label.
Contains the result of a single input parameter validation.