cutelyst 3.9.1
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-2022 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
27
29 bool checkDNS,
31 QString *extractedValue)
32{
33 bool valid = true;
34
35 Diagnose diag = Valid;
36
37 QString _v = value;
38 bool hasRootDot = false;
39 if (_v.endsWith(u'.')) {
40 hasRootDot = true;
41 _v.chop(1);
42 }
43
44 // convert to lower case puny code
46
47 // split up the utf8 string into parts to get the non puny code TLD
48 const QStringList nonAceParts = _v.split(QLatin1Char('.'));
49 if (!nonAceParts.empty()) {
50 const QString tld = nonAceParts.last();
51 if (!tld.isEmpty()) {
52 // there are no TLDs with digits inside, but IDN TLDs can
53 // have digits in their puny code representation, so we have
54 // to check at first if the IDN TLD contains digits before
55 // checking the ACE puny code
56 for (const QChar &ch : tld) {
57 const ushort &uc = ch.unicode();
58 if (((uc > 47) && (uc < 58)) || (uc == 45)) {
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() < 254) {
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() < 64) {
80 bool isTld = (i == (parts.size() - 1));
81 bool isPunyCode = part.startsWith(u"xn--");
82 for (int j = 0; j < part.size(); ++j) {
83 const ushort &uc = part.at(j).unicode();
84 const bool isDigit = ((uc > 47) && (uc < 58));
85 const bool isDash = (uc == 45);
86 // no part/label can start with a digit or a
87 // dash
88 if ((j == 0) && (isDash || isDigit)) {
89 valid = false;
90 diag = isDash ? DashStart : DigitStart;
91 break;
92 }
93 // no part/label can end with a dash
94 if ((j == (part.size() - 1)) && isDash) {
95 valid = false;
96 diag = DashEnd;
97 break;
98 }
99 const bool isChar = ((uc > 96) && (uc < 123));
100 if (!isTld) {
101 // if it is not the tld, it can have a-z 0-9
102 // and -
103 if (!(isDigit || isDash || isChar)) {
104 valid = false;
105 diag = InvalidChars;
106 break;
107 }
108 } else {
109 if (isPunyCode) {
110 if (!(isDigit || isDash || isChar)) {
111 valid = false;
112 diag = InvalidTLD;
113 break;
114 }
115 } else {
116 if (!isChar) {
117 valid = false;
118 diag = InvalidTLD;
119 break;
120 }
121 }
122 }
123 }
124 } else {
125 valid = false;
126 diag = LabelTooLong;
127 break;
128 }
129 } else {
130 valid = false;
131 diag = EmptyLabel;
132 break;
133 }
134 } else {
135 break;
136 }
137 }
138 } else {
139 valid = false;
140 diag = InvalidTLD;
141 }
142 } else {
143 valid = false;
144 diag = InvalidLabelCount;
145 }
146 } else {
147 valid = false;
148 diag = TooLong;
149 }
150 } else {
151 valid = false;
152 diag = EmptyLabel;
153 }
154 }
155 } else {
156 valid = false;
157 diag = EmptyLabel;
158 }
159 } else {
160 valid = false;
161 diag = EmptyLabel;
162 }
163
164 if (valid && checkDNS) {
165 QDnsLookup alookup(QDnsLookup::A, v);
166 QEventLoop aloop;
168 QTimer::singleShot(std::chrono::milliseconds{3100}, &alookup, &QDnsLookup::abort);
169 alookup.lookup();
170 aloop.exec();
171
172 if (((alookup.error() != QDnsLookup::NoError) &&
174 alookup.hostAddressRecords().empty()) {
175 QDnsLookup aaaaLookup(QDnsLookup::AAAA, v);
176 QEventLoop aaaaLoop;
177 QObject::connect(&aaaaLookup, &QDnsLookup::finished, &aaaaLoop, &QEventLoop::quit);
178 QTimer::singleShot(std::chrono::milliseconds{3100}, &aaaaLookup, &QDnsLookup::abort);
179 aaaaLookup.lookup();
180 aaaaLoop.exec();
181
182 if (((aaaaLookup.error() != QDnsLookup::NoError) &&
183 (aaaaLookup.error() != QDnsLookup::OperationCancelledError)) ||
184 aaaaLookup.hostAddressRecords().empty()) {
185 valid = false;
186 diag = MissingDNS;
187 } else if (aaaaLookup.error() == QDnsLookup::OperationCancelledError) {
188 valid = false;
189 diag = DNSTimeout;
190 }
191 } else if (alookup.error() == QDnsLookup::OperationCancelledError) {
192 valid = false;
193 diag = DNSTimeout;
194 }
195 }
196
197 if (diagnose) {
198 *diagnose = diag;
199 }
200
201 if (valid && extractedValue) {
202 if (hasRootDot) {
203 *extractedValue = v + QLatin1Char('.');
204 } else {
205 *extractedValue = v;
206 }
207 }
208
209 return valid;
210}
211
213{
214 QString error;
215
216 if (label.isEmpty()) {
217 switch (diagnose) {
218 case MissingDNS:
219 error = c->translate("Cutelyst::ValidatorDomain",
220 "The domain name seems to be valid but could not be found in the "
221 "domain name system.");
222 break;
223 case InvalidChars:
224 error = c->translate("Cutelyst::ValidatorDomain",
225 "The domain name contains characters that are not allowed.");
226 break;
227 case LabelTooLong:
228 error =
229 c->translate("Cutelyst::ValidatorDomain",
230 "At least one of the sections separated by dots exceeds the maximum "
231 "allowed length of 63 characters. Note that internationalized domain "
232 "names can be longer internally than they are displayed.");
233 break;
234 case TooLong:
235 error = c->translate(
236 "Cutelyst::ValidatorDomain",
237 "The full name of the domain must not be longer than 253 characters. Note that "
238 "internationalized domain names can be longer internally than they are displayed.");
239 break;
241 error = c->translate("Cutelyst::ValidatorDomain",
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 break;
245 case EmptyLabel:
246 error = c->translate("Cutelyst::ValidatorDomain",
247 "At least one of the sections separated by dots is empty. Check "
248 "whether you have entered two dots consecutively.");
249 break;
250 case InvalidTLD:
251 error = c->translate("Cutelyst::ValidatorDomain",
252 "The top level domain (last part) contains characters that are "
253 "not allowed, like digits and/or dashes.");
254 break;
255 case DashStart:
256 error = c->translate("Cutelyst::ValidatorDomain",
257 "Domain name sections are not allowed to start with a dash.");
258 break;
259 case DashEnd:
260 error = c->translate("Cutelyst::ValidatorDomain",
261 "Domain name sections are not allowed to end with a dash.");
262 break;
263 case DigitStart:
264 error = c->translate("Cutelyst::ValidatorDomain",
265 "Domain name sections are not allowed to start with a digit.");
266 break;
267 case Valid:
268 error = c->translate("Cutelyst::ValidatorDomain", "The domain name is valid.");
269 break;
270 case DNSTimeout:
271 error = c->translate("Cutelyst::ValidatorDomain",
272 "The DNS lookup was aborted because it took too long.");
273 break;
274 default:
275 Q_ASSERT_X(false, "domain validation diagnose", "invalid diagnose");
276 break;
277 }
278 } else {
279 switch (diagnose) {
280 case MissingDNS:
281 error = c->translate("Cutelyst::ValidatorDomain",
282 "The domain name in the “%1“ field seems to be valid but could "
283 "not be found in the domain name system.")
284 .arg(label);
285 break;
286 case InvalidChars:
287 error =
288 c->translate(
289 "Cutelyst::ValidatorDomain",
290 "The domain name in the “%1“ field contains characters that are not allowed.")
291 .arg(label);
292 break;
293 case LabelTooLong:
294 error = c->translate("Cutelyst::ValidatorDomain",
295 "The domain name in the “%1“ field is not valid because at least "
296 "one of the sections separated by dots exceeds the maximum "
297 "allowed length of 63 characters. Note that internationalized "
298 "domain names can be longer internally than they are displayed.")
299 .arg(label);
300 break;
301 case TooLong:
302 error = c->translate("Cutelyst::ValidatorDomain",
303 "The full name of the domain in the “%1” field must not be longer "
304 "than 253 characters. Note that internationalized domain names "
305 "can be longer internally than they are displayed.")
306 .arg(label);
307 break;
309 error = c->translate("Cutelyst::ValidatorDomain",
310 "The “%1” field does not contain a valid domain name because it "
311 "has either no parts (is empty) or only has a top level domain.")
312 .arg(label);
313 break;
314 case EmptyLabel:
315 error = c->translate("Cutelyst::ValidatorDomain",
316 "The domain name in the “%1“ field is not valid because at least "
317 "one of the sections separated by dots is empty. Check whether "
318 "you have entered two dots consecutively.")
319 .arg(label);
320 break;
321 case InvalidTLD:
322 error = c->translate(
323 "Cutelyst::ValidatorDomain",
324 "The top level domain (last part) of the domain name in the “%1” field "
325 "contains characters that are not allowed, like digits and or dashes.")
326 .arg(label);
327 break;
328 case DashStart:
329 error = c->translate("Cutelyst::ValidatorDomain",
330 "The domain name in the “%1“ field is not valid because domain "
331 "name sections are not allowed to start with a dash.")
332 .arg(label);
333 break;
334 case DashEnd:
335 error = c->translate("Cutelyst::ValidatorDomain",
336 "The domain name in the “%1“ field is not valid because domain "
337 "name sections are not allowed to end with a dash.")
338 .arg(label);
339 break;
340 case DigitStart:
341 error = c->translate("Cutelyst::ValidatorDomain",
342 "The domain name in the “%1“ field is not valid because domain "
343 "name sections are not allowed to start with a digit.")
344 .arg(label);
345 break;
346 case Valid:
347 error = c->translate("Cutelyst::ValidatorDomain",
348 "The domain name in the “%1” field is valid.")
349 .arg(label);
350 break;
351 case DNSTimeout:
352 error = c->translate("Cutelyst::ValidatorDomain",
353 "The DNS lookup for the domain name in the “%1” field was aborted "
354 "because it took too long.")
355 .arg(label);
356 break;
357 default:
358 Q_ASSERT_X(false, "domain validation diagnose", "invalid diagnose");
359 break;
360 }
361 }
362
363 return error;
364}
365
367{
368 ValidatorReturnType result;
369
370 const QString &v = value(params);
371
372 if (!v.isEmpty()) {
373 Q_D(const ValidatorDomain);
374 QString exVal;
375 Diagnose diag;
376 if (ValidatorDomain::validate(v, d->checkDNS, &diag, &exVal)) {
377 result.value.setValue(exVal);
378 } else {
379 result.errorMessage = validationError(c, diag);
380 }
381 } else {
382 defaultValue(c, &result, "ValidatorDomain");
383 }
384
385 return result;
386}
387
389{
390 QString error;
391 const QString _label = label(c);
392 const Diagnose diag = errorData.value<Diagnose>();
393 error = ValidatorDomain::diagnoseString(c, diag, _label);
394 return error;
395}
396
397#include "moc_validatordomain.cpp"
The Cutelyst Context.
Definition context.h:39
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition context.cpp:490
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())
Returns a human readable description of a Diagnose.
ValidatorDomain(const QString &field, bool checkDNS=false, const ValidatorMessages &messages=ValidatorMessages(), const QString &defValKey=QString())
Constructs a new ValidatorDomain with the given parameters.
~ValidatorDomain() override
Deconstructs ValidatorDomain.
Diagnose
Possible diagnose information for the checked domain.
QString label(Context *c) const
Returns the human readable field label used for generic error messages.
QString field() const
Returns the name of the field to validate.
void defaultValue(Context *c, ValidatorReturnType *result, const char *validatorName) const
I a defValKey has been set in the constructor, this will try to get the default value from the stash ...
ValidatorRule(const QString &field, const ValidatorMessages &messages=ValidatorMessages(), const QString &defValKey=QString())
Constructs a new ValidatorRule with the given parameters.
QString value(const ParamsMultiMap &params) const
Returns the value of the field from the input params.
QString validationError(Context *c, const QVariant &errorData=QVariant()) const
Returns a descriptive error message if validation failed.
static bool validate(const QString &value, bool checkDNS, Diagnose *diagnose=nullptr, QString *extractedValue=nullptr)
Returns true if value is a valid domain name.
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QMultiMap< QString, QString > ParamsMultiMap
ushort unicode() const const
void abort()
void finished()
QList< QDnsHostAddressRecord > hostAddressRecords() const const
void lookup()
int exec(ProcessEventsFlags flags)
void quit()
const T & at(int i) const const
bool empty() const const
T & last()
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
void chop(int n)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
int length() const const
int size() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString toLower() const const
KeepEmptyParts
QByteArray toAce(const QString &domain)
void setValue(const T &value)
T value() const const
Stores custom error messages and the input field label.
Contains the result of a single input parameter validation.