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 
14 using 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 {
26 }
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) &&
173  (alookup.error() != QDnsLookup::OperationCancelledError)) ||
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;
240  case InvalidLabelCount:
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;
308  case InvalidLabelCount:
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"
QString genericValidationError(Context *c, const QVariant &errorData=QVariant()) const override
Returns a generic error message if validation failed.
void quit()
Checks if the value of the input field contains FQDN according to RFC 1035.
QString validationError(Context *c, const QVariant &errorData=QVariant()) const
Returns a descriptive error message if validation failed.
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
Stores custom error messages and the input field label.
const_reference at(qsizetype i) const const
qsizetype size() const const
T value() const const
void chop(qsizetype n)
qsizetype size() const const
void finished()
The Cutelyst Context.
Definition: context.h:38
void abort()
bool empty() const const
int exec(ProcessEventsFlags flags)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toAce(const QString &domain, AceProcessingOptions options)
bool isEmpty() const const
void lookup()
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:490
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
static QString diagnoseString(Context *c, Diagnose diagnose, const QString &label=QString())
Returns a human readable description of a Diagnose.
Base class for all validator rules.
char16_t & unicode()
Diagnose
Possible diagnose information for the checked domain.
QString label(Context *c) const
Returns the human readable field label used for generic error messages.
ValidatorDomain(const QString &field, bool checkDNS=false, const ValidatorMessages &messages=ValidatorMessages(), const QString &defValKey=QString())
Constructs a new ValidatorDomain with the given parameters.
QString toLower() const const
QList< QDnsHostAddressRecord > hostAddressRecords() const const
KeepEmptyParts
QString value(const ParamsMultiMap &params) const
Returns the value of the field from the input params.
static bool validate(const QString &value, bool checkDNS, Diagnose *diagnose=nullptr, QString *extractedValue=nullptr)
Returns true if value is a valid domain name.
QString fromLatin1(QByteArrayView str)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
const QChar at(qsizetype position) const const
T & last()
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype length() const const
~ValidatorDomain() override
Deconstructs ValidatorDomain.
Contains the result of a single input parameter validation.
Definition: validatorrule.h:49
QString arg(Args &&... args) const const
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 ...
void setValue(QVariant &&value)