Cutelyst  3.1.0
validatordomain.cpp
1 /*
2  * Copyright (C) 2018 Matthias Fehring <kontakt@buschmann23.de>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
19 #include "validatordomain_p.h"
20 #include <QUrl>
21 #include <QStringList>
22 #include <QEventLoop>
23 #include <QDnsLookup>
24 #include <QTimer>
25 
26 using namespace Cutelyst;
27 
28 ValidatorDomain::ValidatorDomain(const QString &field, bool checkDNS, const ValidatorMessages &messages, const QString &defValKey) :
29  ValidatorRule(* new ValidatorDomainPrivate(field, checkDNS, messages, defValKey))
30 {
31 }
32 
34 {
35 }
36 
37 bool ValidatorDomain::validate(const QString &value, bool checkDNS, Cutelyst::ValidatorDomain::Diagnose *diagnose, QString *extractedValue)
38 {
39  bool valid = true;
40 
41  Diagnose diag = Valid;
42 
43  QString _v = value;
44  bool hasRootDot = false;
45  if (_v.endsWith(QLatin1Char('.'))) {
46  hasRootDot = true;
47  _v.chop(1);
48  }
49 
50  // convert to lower case puny code
52 
53  // split up the utf8 string into parts to get the non puny code TLD
54  const QStringList nonAceParts = _v.split(QLatin1Char('.'));
55  if (!nonAceParts.empty()) {
56  const QString tld = nonAceParts.last();
57  if (!tld.isEmpty()) {
58  // there are no TLDs with digits inside, but IDN TLDs can
59  // have digits in their puny code representation, so we have
60  // to check at first if the IDN TLD contains digits before
61  // checking the ACE puny code
62  for (const QChar &ch : tld) {
63  const ushort &uc = ch.unicode();
64  if (((uc > 47) && (uc < 58)) || (uc == 45)) {
65  diag = InvalidTLD;
66  valid = false;
67  break;
68  }
69  }
70 
71  if (valid) {
72  if (!v.isEmpty()) {
73  // maximum length of the name in the DNS is 253 without the last dot
74  if (v.length() < 254) {
75 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
76  const QStringList parts = v.split(QLatin1Char('.'), Qt::KeepEmptyParts);
77 #else
79 #endif
80  // there has to be more than only the TLD
81  if (parts.size() > 1) {
82  // the TLD can not have only 1 char
83  if (parts.last().length() > 1) {
84  for (int i = 0; i < parts.size(); ++i) {
85  if (valid) {
86  const QString part = parts.at(i);
87  if (!part.isEmpty()) {
88  // labels/parts can have a maximum length of 63 chars
89  if (part.length() < 64) {
90  bool isTld = (i == (parts.size() -1));
91  bool isPunyCode = part.startsWith(QLatin1String("xn--"));
92  for (int j = 0; j < part.size(); ++j) {
93  const ushort &uc = part.at(j).unicode();
94  const bool isDigit = ((uc > 47) && (uc < 58));
95  const bool isDash = (uc == 45);
96  // no part/label can start with a digit or a dash
97  if ((j == 0) && (isDash || isDigit)) {
98  valid = false;
99  diag = isDash ? DashStart : DigitStart;
100  break;
101  }
102  // no part/label can end with a dash
103  if ((j == (part.size() - 1)) && isDash) {
104  valid = false;
105  diag = DashEnd;
106  break;
107  }
108  const bool isChar = ((uc > 96) && (uc < 123));
109  if (!isTld) {
110  // if it is not the tld, it can have a-z 0-9 and -
111  if (!(isDigit || isDash || isChar)) {
112  valid = false;
113  diag = InvalidChars;
114  break;
115  }
116  } else {
117  if (isPunyCode) {
118  if (!(isDigit || isDash || isChar)) {
119  valid = false;
120  diag = InvalidTLD;
121  break;
122  }
123  } else {
124  if (!isChar) {
125  valid = false;
126  diag = InvalidTLD;
127  break;
128  }
129  }
130  }
131  }
132  } else {
133  valid = false;
134  diag = LabelTooLong;
135  break;
136  }
137  } else {
138  valid = false;
139  diag = EmptyLabel;
140  break;
141  }
142  } else {
143  break;
144  }
145  }
146  } else {
147  valid = false;
148  diag = InvalidTLD;
149  }
150  } else {
151  valid = false;
152  diag = InvalidLabelCount;
153  }
154  } else {
155  valid = false;
156  diag = TooLong;
157  }
158  } else {
159  valid = false;
160  diag = EmptyLabel;
161  }
162  }
163  } else {
164  valid = false;
165  diag = EmptyLabel;
166  }
167  } else {
168  valid = false;
169  diag = EmptyLabel;
170  }
171 
172 
173  if (valid && checkDNS) {
174  QDnsLookup alookup(QDnsLookup::A, v);
175  QEventLoop aloop;
177  QTimer::singleShot(3100, &alookup, &QDnsLookup::abort);
178  alookup.lookup();
179  aloop.exec();
180 
181  if (((alookup.error() != QDnsLookup::NoError) && (alookup.error() != QDnsLookup::OperationCancelledError)) || alookup.hostAddressRecords().empty()) {
182  QDnsLookup aaaaLookup(QDnsLookup::AAAA, v);
183  QEventLoop aaaaLoop;
184  QObject::connect(&aaaaLookup, &QDnsLookup::finished, &aaaaLoop, &QEventLoop::quit);
185  QTimer::singleShot(3100, &aaaaLookup, &QDnsLookup::abort);
186  aaaaLookup.lookup();
187  aaaaLoop.exec();
188 
189  if (((aaaaLookup.error() != QDnsLookup::NoError) && (aaaaLookup.error() != QDnsLookup::OperationCancelledError)) || aaaaLookup.hostAddressRecords().empty()) {
190  valid = false;
191  diag = MissingDNS;
192  } else if (aaaaLookup.error() == QDnsLookup::OperationCancelledError) {
193  valid = false;
194  diag = DNSTimeout;
195  }
196  } else if (alookup.error() == QDnsLookup::OperationCancelledError) {
197  valid = false;
198  diag = DNSTimeout;
199  }
200  }
201 
202  if (diagnose) {
203  *diagnose = diag;
204  }
205 
206  if (valid && extractedValue) {
207  if (hasRootDot) {
208  *extractedValue = v + QLatin1Char('.');
209  } else {
210  *extractedValue = v;
211  }
212  }
213 
214  return valid;
215 }
216 
218 {
219  QString error;
220 
221  if (label.isEmpty()) {
222  switch (diagnose) {
223  case MissingDNS:
224  error = c->translate("Cutelyst::ValidatorDomain", "The domain name seems to be valid but could not be found in the domain name system.");
225  break;
226  case InvalidChars:
227  error = c->translate("Cutelyst::ValidatorDomain", "The domain name contains characters that are not allowed.");
228  break;
229  case LabelTooLong:
230  error = c->translate("Cutelyst::ValidatorDomain", "At least one of the sections separated by dots exceeds the maximum allowed length of 63 characters. Note that internationalized domain names can be longer internally than they are displayed.");
231  break;
232  case TooLong:
233  error = c->translate("Cutelyst::ValidatorDomain", "The full name of the domain must not be longer than 253 characters. Note that internationalized domain names can be longer internally than they are displayed.");
234  break;
235  case InvalidLabelCount:
236  error = c->translate("Cutelyst::ValidatorDomain", "This is not a valid domain name because it has either no parts (is empty) or only has a top level domain.");
237  break;
238  case EmptyLabel:
239  error = c->translate("Cutelyst::ValidatorDomain", "At least one of the sections separated by dots is empty. Check whether you have entered two dots consecutively.");
240  break;
241  case InvalidTLD:
242  error = c->translate("Cutelyst::ValidatorDomain", "The top level domain (last part) contains characters that are not allowed, like digits and/or dashes.");
243  break;
244  case DashStart:
245  error = c->translate("Cutelyst::ValidatorDomain", "Domain name sections are not allowed to start with a dash.");
246  break;
247  case DashEnd:
248  error = c->translate("Cutelyst::ValidatorDomain", "Domain name sections are not allowed to end with a dash.");
249  break;
250  case DigitStart:
251  error = c->translate("Cutelyst::ValidatorDomain", "Domain name sections are not allowed to start with a digit.");
252  break;
253  case Valid:
254  error = c->translate("Cutelyst::ValidatorDomain", "The domain name is valid.");
255  break;
256  case DNSTimeout:
257  error = c->translate("Cutelyst::ValidatorDomain", "The DNS lookup was aborted because it took too long.");
258  break;
259  default:
260  Q_ASSERT_X(false, "domain validation diagnose", "invalid diagnose");
261  break;
262  }
263  } else {
264  switch (diagnose) {
265  case MissingDNS:
266  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1“ field seems to be valid but could not be found in the domain name system.").arg(label);
267  break;
268  case InvalidChars:
269  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1“ field contains characters that are not allowed.").arg(label);
270  break;
271  case LabelTooLong:
272  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1“ field is not valid because at least one of the sections separated by dots exceeds the maximum allowed length of 63 characters. Note that internationalized domain names can be longer internally than they are displayed.").arg(label);
273  break;
274  case TooLong:
275  error = c->translate("Cutelyst::ValidatorDomain", "The full name of the domain in the “%1” field must not be longer than 253 characters. Note that internationalized domain names can be longer internally than they are displayed.").arg(label);
276  break;
277  case InvalidLabelCount:
278  error = c->translate("Cutelyst::ValidatorDomain", "The “%1” field does not contain a valid domain name because it has either no parts (is empty) or only has a top level domain.").arg(label);
279  break;
280  case EmptyLabel:
281  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1“ field is not valid because at least one of the sections separated by dots is empty. Check whether you have entered two dots consecutively.").arg(label);
282  break;
283  case InvalidTLD:
284  error = c->translate("Cutelyst::ValidatorDomain", "The top level domain (last part) of the domain name in the “%1” field contains characters that are not allowed, like digits and or dashes.").arg(label);
285  break;
286  case DashStart:
287  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1“ field is not valid because domain name sections are not allowed to start with a dash.").arg(label);
288  break;
289  case DashEnd:
290  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1“ field is not valid because domain name sections are not allowed to end with a dash.").arg(label);
291  break;
292  case DigitStart:
293  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1“ field is not valid because domain name sections are not allowed to start with a digit.").arg(label);
294  break;
295  case Valid:
296  error = c->translate("Cutelyst::ValidatorDomain", "The domain name in the “%1” field is valid.").arg(label);
297  break;
298  case DNSTimeout:
299  error = c->translate("Cutelyst::ValidatorDomain", "The DNS lookup for the domain name in the “%1” field was aborted because it took too long.").arg(label);
300  break;
301  default:
302  Q_ASSERT_X(false, "domain validation diagnose", "invalid diagnose");
303  break;
304  }
305  }
306 
307  return error;
308 }
309 
311 {
312  ValidatorReturnType result;
313 
314  const QString &v = value(params);
315 
316  if (!v.isEmpty()) {
317  Q_D(const ValidatorDomain);
318  QString exVal;
319  Diagnose diag;
320  if (ValidatorDomain::validate(v, d->checkDNS, &diag, &exVal)) {
321  result.value.setValue(exVal);
322  } else {
323  result.errorMessage = validationError(c, diag);
324  }
325  } else {
326  defaultValue(c, &result, "ValidatorDomain");
327  }
328 
329  return result;
330 }
331 
333 {
334  QString error;
335  const QString _label = label(c);
336  const Diagnose diag = errorData.value<Diagnose>();
337  error = ValidatorDomain::diagnoseString(c, diag, _label);
338  return error;
339 }
340 
341 #include "moc_validatordomain.cpp"
The Cutelyst Context.
Definition: context.h:52
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:480
Checks if the value of the input field contains 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())
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.
Base class for all validator rules.
QString label(Context *c) const
Returns the human readable field label used for generic error messages.
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 ...
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
ushort unicode() const const
void abort()
void finished()
QList< QDnsHostAddressRecord > hostAddressRecords() const const
void lookup()
int exec(QEventLoop::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, QString::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.
Definition: validatorrule.h:62