cutelyst  4.5.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
validatoremail.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2017-2023 Matthias Fehring <mf@huessenbergnetz.de>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 
6 #include "validatoremail_p.h"
7 
8 #include <algorithm>
9 #include <functional>
10 
11 #include <QDnsLookup>
12 #include <QEventLoop>
13 #include <QTimer>
14 #include <QUrl>
15 
16 using namespace Cutelyst;
17 
18 const QRegularExpression ValidatorEmailPrivate::ipv4Regex{
19  u"\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25["
20  "0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"_qs};
21 const QRegularExpression ValidatorEmailPrivate::ipv6PartRegex{u"^[0-9A-Fa-f]{0,4}$"_qs};
22 const QString ValidatorEmailPrivate::stringSpecials{u"()<>[]:;@\\,.\""_qs};
23 
25  Category threshold,
26  Options options,
27  const Cutelyst::ValidatorMessages &messages,
28  const QString &defValKey)
29  : ValidatorRule(*new ValidatorEmailPrivate(field, threshold, options, messages, defValKey))
30 {
31 }
32 
34 
36 {
37  ValidatorReturnType result;
38 
39  const QString v = value(params);
40 
41  Q_D(const ValidatorEmail);
42 
43  if (!v.isEmpty()) {
44 
45  // QString email;
46  // const int atPos = v.lastIndexOf(QLatin1Char('@'));
47  // if (atPos > 0) {
48  // const QStringRef local = v.leftRef(atPos);
49  // const QString domain = v.mid(atPos + 1);
50  // bool asciiDomain = true;
51  // for (const QChar &ch : domain) {
52  // const ushort &uc = ch.unicode();
53  // if (uc > 127) {
54  // asciiDomain = false;
55  // break;
56  // }
57  // }
58 
59  // if (asciiDomain) {
60  // email = v;
61  // } else {
62  // email = local + QLatin1Char('@') +
63  // QString::fromLatin1(QUrl::toAce(domain));
64  // }
65  // } else {
66  // email = v;
67  // }
68 
69  ValidatorEmailDiagnoseStruct diag;
70 
71  if (ValidatorEmailPrivate::checkEmail(v, d->options, d->threshold, &diag)) {
72  if (!diag.literal.isEmpty()) {
73  result.value.setValue<QString>(diag.localpart + QLatin1Char('@') + diag.literal);
74  } else {
75  result.value.setValue<QString>(diag.localpart + QLatin1Char('@') + diag.domain);
76  }
77  } else {
78  result.errorMessage =
79  validationError(c, QVariant::fromValue<Diagnose>(diag.finalStatus));
80  }
81 
82  result.extra = QVariant::fromValue<QList<Diagnose>>(diag.returnStatus);
83 
84  } else {
85  defaultValue(c, &result);
86  }
87 
88  return result;
89 }
90 
92 {
93  QString error;
94 
95  error = ValidatorEmail::diagnoseString(c, errorData.value<Diagnose>(), label(c));
96 
97  return error;
98 }
99 
100 bool ValidatorEmailPrivate::checkEmail(const QString &address,
101  ValidatorEmail::Options options,
102  ValidatorEmail::Category threshold,
103  ValidatorEmailDiagnoseStruct *diagnoseStruct)
104 {
106 
107  EmailPart context = ComponentLocalpart;
108  QList<EmailPart> contextStack{context};
109  EmailPart contextPrior = ComponentLocalpart;
110 
111  QChar token;
112  QChar tokenPrior;
113 
114  QString parseLocalPart;
115  QString parseDomain;
116  QString parseLiteral;
117  QMap<int, QString> atomListLocalPart;
118  QMap<int, QString> atomListDomain;
119  int elementCount = 0;
120  int elementLen = 0;
121  bool hypenFlag = false;
122  bool endOrDie = false;
123  int crlf_count = 0;
124 
125  const bool checkDns = options.testFlag(ValidatorEmail::CheckDNS);
126  const bool allowUtf8Local = options.testFlag(ValidatorEmail::UTF8Local);
127  const bool allowIdn = options.testFlag(ValidatorEmail::AllowIDN);
128 
129  QString email;
130  const qsizetype atPos = address.lastIndexOf(QLatin1Char('@'));
131  if (allowIdn) {
132  if (atPos > 0) {
133  const QString local = address.left(atPos);
134  const QString domain = address.mid(atPos + 1);
135  bool asciiDomain = true;
136  for (const QChar &ch : domain) {
137  const ushort &uc = ch.unicode();
138  if (uc > ValidatorEmailPrivate::asciiEnd) {
139  asciiDomain = false;
140  break;
141  }
142  }
143 
144  if (asciiDomain) {
145  email = address;
146  } else {
147  email = local + QLatin1Char('@') + QString::fromLatin1(QUrl::toAce(domain));
148  }
149  } else {
150  email = address;
151  }
152  } else {
153  email = address;
154  }
155 
156  const qsizetype rawLength = email.length();
157 
158  for (int i = 0; i < rawLength; i++) {
159  token = email[i];
160 
161  switch (context) {
162  //-------------------------------------------------------------
163  // local-part
164  //-------------------------------------------------------------
165  case ComponentLocalpart:
166  {
167  // https://tools.ietf.org/html/rfc5322#section-3.4.1
168  // local-part = dot-atom / quoted-string / obs-local-part
169  //
170  // dot-atom = [CFWS] dot-atom-text [CFWS]
171  //
172  // dot-atom-text = 1*atext *("." 1*atext)
173  //
174  // quoted-string = [CFWS]
175  // DQUOTE *([FWS] qcontent) [FWS] DQUOTE
176  // [CFWS]
177  //
178  // obs-local-part = word *("." word)
179  //
180  // word = atom / quoted-string
181  //
182  // atom = [CFWS] 1*atext [CFWS]
183 
184  if (token == QLatin1Char('(')) { // comment
185  if (elementLen == 0) {
186  // Comments are OK at the beginning of an element
187  returnStatus.push_back((elementCount == 0) ? ValidatorEmail::CFWSComment
189  } else {
190  returnStatus.push_back(ValidatorEmail::CFWSComment);
191  endOrDie = true; // We can't start a comment in the middle of an element, so
192  // this better be the end
193  }
194 
195  contextStack.push_back(context);
196  context = ContextComment;
197  } else if (token == QLatin1Char('.')) { // Next dot-atom element
198  if (elementLen == 0) {
199  // Another dot, already?
200  returnStatus.push_back((elementCount == 0)
203  } else {
204  // The entire local part can be a quoted string for RFC 5321
205  // If it's just one atom that is quoten then it's an RFC 5322 obsolete form
206  if (endOrDie) {
207  returnStatus.push_back(ValidatorEmail::DeprecatedLocalpart);
208  }
209  }
210 
211  endOrDie = false; // CFWS & quoted strings are OK again now we're at the beginning
212  // of an element (although they are obsolete forms)
213  elementLen = 0;
214  elementCount++;
215  parseLocalPart += token;
216  atomListLocalPart[elementCount] = QString();
217  } else if (token == QLatin1Char('"')) {
218  if (elementLen == 0) {
219  // The entire local-part can be a quoted string for RFC 5321
220  // If it's just one atom that is quoted then it's an RFC 5322 obsolete form
221  returnStatus.push_back((elementCount == 0)
224 
225  parseLocalPart += token;
226  atomListLocalPart[elementCount] += token;
227  elementLen++;
228  endOrDie = true; // quoted string must be the entire element
229  contextStack.push_back(context);
230  context = ContextQuotedString;
231  } else {
232  returnStatus.push_back(ValidatorEmail::ErrorExpectingAText); // Fatal error
233  }
234  } else if ((token == QChar(QChar::CarriageReturn)) || (token == QChar(QChar::Space)) ||
235  (token == QChar(QChar::Tabulation))) { // Folding White Space
236  if ((token == QChar(QChar::CarriageReturn)) &&
237  ((++i == rawLength) || (email[i] != QChar(QChar::LineFeed)))) {
238  returnStatus.push_back(ValidatorEmail::ErrorCRnoLF);
239  break;
240  }
241 
242  if (elementLen == 0) {
243  returnStatus.push_back((elementCount == 0) ? ValidatorEmail::CFWSFWS
245  } else {
246  endOrDie = true; // We can't start FWS in the middle of an element, so this
247  // better be the end
248  }
249 
250  contextStack.push_back(context);
251  context = ContextFWS;
252  tokenPrior = token;
253  } else if (token == QLatin1Char('@')) {
254  // At this point we should have a valid local part
255  if (contextStack.size() != 1) {
256  returnStatus.push_back(ValidatorEmail::ErrorFatal);
257  qCCritical(C_VALIDATOR) << "ValidatorEmail: Unexpected item on context stack";
258  break;
259  }
260 
261  if (parseLocalPart.isEmpty()) {
262  returnStatus.push_back(ValidatorEmail::ErrorNoLocalPart); // Fatal error
263  } else if (elementLen == 0) {
264  returnStatus.push_back(ValidatorEmail::ErrorDotEnd); // Fatal Error
265  } else if (parseLocalPart.size() > ValidatorEmailPrivate::maxLocalPartLength) {
266  // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
267  // The maximum total length of a user name or other local-part is 64
268  // octets.
269  returnStatus.push_back(ValidatorEmail::RFC5322LocalTooLong);
270  } else if ((contextPrior == ContextComment) || (contextPrior == ContextFWS)) {
271  // https://tools.ietf.org/html/rfc5322#section-3.4.1
272  // Comments and folding white space
273  // SHOULD NOT be used around the "@" in the addr-spec.
274  //
275  // https://tools.ietf.org/html/rfc2119
276  // 4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that
277  // there may exist valid reasons in particular circumstances when the
278  // particular behavior is acceptable or even useful, but the full
279  // implications should be understood and the case carefully weighed
280  // before implementing any behavior described with this label.
281  returnStatus.push_back(ValidatorEmail::DeprecatedCFWSNearAt);
282  }
283 
284  context = ComponentDomain;
285  contextStack.clear();
286  contextStack.push_back(context);
287  elementCount = 0;
288  elementLen = 0;
289  endOrDie = false;
290 
291  } else { // atext
292  // https://tools.ietf.org/html/rfc5322#section-3.2.3
293  // atext = ALPHA / DIGIT / ; Printable US-ASCII
294  // "!" / "#" / ; characters not including
295  // "$" / "%" / ; specials. Used for atoms.
296  // "&" / "'" /
297  // "*" / "+" /
298  // "-" / "/" /
299  // "=" / "?" /
300  // "^" / "_" /
301  // "`" / "{" /
302  // "|" / "}" /
303  //
304  if (endOrDie) {
305  switch (contextPrior) {
306  case ContextComment:
307  case ContextFWS:
308  returnStatus.push_back(ValidatorEmail::ErrorATextAfterCFWS);
309  break;
310  case ContextQuotedString:
311  returnStatus.push_back(ValidatorEmail::ErrorATextAfterQS);
312  break;
313  default:
314  returnStatus.push_back(ValidatorEmail::ErrorFatal);
315  qCCritical(C_VALIDATOR)
316  << "ValidatorEmail: More atext found where none is allowed, "
317  "but unrecognizes prior context";
318  break;
319  }
320  } else {
321  contextPrior = context;
322  const char16_t uni = token.unicode();
323 
324  if (!allowUtf8Local) {
325  if ((uni < ValidatorEmailPrivate::asciiExclamationMark) ||
326  (uni > ValidatorEmailPrivate::asciiTilde) ||
327  ValidatorEmailPrivate::stringSpecials.contains(token)) {
328  returnStatus.push_back(
329  ValidatorEmail::ErrorExpectingAText); // fatal error
330  }
331  } else {
332  if (!token.isLetterOrNumber()) {
333  if ((uni < ValidatorEmailPrivate::asciiExclamationMark) ||
334  (uni > ValidatorEmailPrivate::asciiTilde) ||
335  ValidatorEmailPrivate::stringSpecials.contains(token)) {
336  returnStatus.push_back(
337  ValidatorEmail::ErrorExpectingAText); // fatal error
338  }
339  }
340  }
341 
342  parseLocalPart += token;
343  atomListLocalPart[elementCount] += token;
344  elementLen++;
345  }
346  }
347  } break;
348  //-----------------------------------------
349  // Domain
350  //-----------------------------------------
351  case ComponentDomain:
352  {
353  // https://tools.ietf.org/html/rfc5322#section-3.4.1
354  // domain = dot-atom / domain-literal / obs-domain
355  //
356  // dot-atom = [CFWS] dot-atom-text [CFWS]
357  //
358  // dot-atom-text = 1*atext *("." 1*atext)
359  //
360  // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
361  //
362  // dtext = %d33-90 / ; Printable US-ASCII
363  // %d94-126 / ; characters not including
364  // obs-dtext ; "[", "]", or "\"
365  //
366  // obs-domain = atom *("." atom)
367  //
368  // atom = [CFWS] 1*atext [CFWS]
369  // https://tools.ietf.org/html/rfc5321#section-4.1.2
370  // Mailbox = Local-part "@" ( Domain / address-literal )
371  //
372  // Domain = sub-domain *("." sub-domain)
373  //
374  // address-literal = "[" ( IPv4-address-literal /
375  // IPv6-address-literal /
376  // General-address-literal ) "]"
377  // ; See Section 4.1.3
378  // https://tools.ietf.org/html/rfc5322#section-3.4.1
379  // Note: A liberal syntax for the domain portion of addr-spec is
380  // given here. However, the domain portion contains addressing
381  // information specified by and used in other protocols (e.g.,
382  // [RFC1034], [RFC1035], [RFC1123], [RFC5321]). It is therefore
383  // incumbent upon implementations to conform to the syntax of
384  // addresses for the context in which they are used.
385  // is_email() author's note: it's not clear how to interpret this in
386  // the context of a general email address validator. The conclusion I
387  // have reached is this: "addressing information" must comply with
388  // RFC 5321 (and in turn RFC 1035), anything that is "semantically
389  // invisible" must comply only with RFC 5322.
390 
391  if (token == QLatin1Char('(')) { // comment
392  if (elementLen == 0) {
393  // Comments at the start of the domain are deprecated in the text
394  // Comments at the start of a subdomain are obs-domain
395  // (https://tools.ietf.org/html/rfc5322#section-3.4.1)
396  returnStatus.push_back((elementCount == 0)
399  } else {
400  returnStatus.push_back(ValidatorEmail::CFWSComment);
401  endOrDie = true; // We can't start a comment in the middle of an element, so
402  // this better be the end
403  }
404 
405  contextStack.push_back(context);
406  context = ContextComment;
407  } else if (token == QLatin1Char('.')) { // next dot-atom element
408  if (elementLen == 0) {
409  // another dot, already?
410  returnStatus.push_back((elementCount == 0)
413  } else if (hypenFlag) {
414  // Previous subdomain ended in a hyphen
415  returnStatus.push_back(ValidatorEmail::ErrorDomainHyphenEnd); // fatal error
416  } else {
417  // Nowhere in RFC 5321 does it say explicitly that the
418  // domain part of a Mailbox must be a valid domain according
419  // to the DNS standards set out in RFC 1035, but this *is*
420  // implied in several places. For instance, wherever the idea
421  // of host routing is discussed the RFC says that the domain
422  // must be looked up in the DNS. This would be nonsense unless
423  // the domain was designed to be a valid DNS domain. Hence we
424  // must conclude that the RFC 1035 restriction on label length
425  // also applies to RFC 5321 domains.
426  //
427  // https://tools.ietf.org/html/rfc1035#section-2.3.4
428  // labels 63 octets or less
429  if (elementLen > ValidatorEmailPrivate::maxDnsLabelLength) {
430  returnStatus.push_back(ValidatorEmail::RFC5322LabelTooLong);
431  }
432  }
433 
434  endOrDie = false; // CFWS is OK again now we're at the beginning of an element
435  // (although it may be obsolete CFWS)
436  elementLen = 0;
437  elementCount++;
438  atomListDomain[elementCount] = QString();
439  parseDomain += token;
440 
441  } else if (token == QLatin1Char('[')) { // Domain literal
442  if (parseDomain.isEmpty()) {
443  endOrDie = true; // domain literal must be the only component
444  elementLen++;
445  contextStack.push_back(context);
446  context = ComponentLiteral;
447  parseDomain += token;
448  atomListDomain[elementCount] += token;
449  parseLiteral = QString();
450  } else {
451  returnStatus.push_back(ValidatorEmail::ErrorExpectingAText); // Fatal error
452  }
453  } else if ((token == QChar(QChar::CarriageReturn)) || (token == QChar(QChar::Space)) ||
454  (token == QChar(QChar::Tabulation))) { // Folding White Space
455  if ((token == QChar(QChar::CarriageReturn)) &&
456  ((++i == rawLength) || email[i] != QChar(QChar::LineFeed))) {
457  returnStatus.push_back(ValidatorEmail::ErrorCRnoLF); // Fatal error
458  break;
459  }
460 
461  if (elementLen == 0) {
462  returnStatus.push_back((elementCount == 0)
465  } else {
466  returnStatus.push_back(ValidatorEmail::CFWSFWS);
467  endOrDie = true; // We can't start FWS in the middle of an element, so this
468  // better be the end
469  }
470 
471  contextStack.push_back(context);
472  context = ContextFWS;
473  tokenPrior = token;
474 
475  } else { // atext
476  // RFC 5322 allows any atext...
477  // https://tools.ietf.org/html/rfc5322#section-3.2.3
478  // atext = ALPHA / DIGIT / ; Printable US-ASCII
479  // "!" / "#" / ; characters not including
480  // "$" / "%" / ; specials. Used for atoms.
481  // "&" / "'" /
482  // "*" / "+" /
483  // "-" / "/" /
484  // "=" / "?" /
485  // "^" / "_" /
486  // "`" / "{" /
487  // "|" / "}" /
488  // "~"
489  // But RFC 5321 only allows letter-digit-hyphen to comply with DNS rules (RFCs 1034
490  // & 1123) https://tools.ietf.org/html/rfc5321#section-4.1.2
491  // sub-domain = Let-dig [Ldh-str]
492  //
493  // Let-dig = ALPHA / DIGIT
494  //
495  // Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig
496  //
497 
498  if (endOrDie) {
499  // We have encountered atext where it is no longer valid
500  switch (contextPrior) {
501  case ContextComment:
502  case ContextFWS:
503  returnStatus.push_back(ValidatorEmail::ErrorATextAfterCFWS);
504  break;
505  case ComponentLiteral:
506  returnStatus.push_back(ValidatorEmail::ErrorATextAfterDomLit);
507  break;
508  default:
509  returnStatus.push_back(ValidatorEmail::ErrorFatal);
510  qCCritical(C_VALIDATOR)
511  << "ValidatorEmail: More atext found where none is allowed, but"
512  << "unrecognised prior context.";
513  break;
514  }
515  }
516 
517  const char16_t uni = token.unicode();
518  hypenFlag = false; // Assume this token isn't a hyphen unless we discover it is
519 
520  if ((uni < ValidatorEmailPrivate::asciiExclamationMark) ||
521  (uni > ValidatorEmailPrivate::asciiTilde) ||
522  ValidatorEmailPrivate::stringSpecials.contains(token)) {
523  returnStatus.push_back(ValidatorEmail::ErrorExpectingAText); // Fatal error
524  } else if (token == QLatin1Char('-')) {
525  if (elementLen == 0) {
526  // Hyphens can't be at the beggining of a subdomain
527  returnStatus.push_back(
529  }
530  hypenFlag = true;
531  } else if (!(((uni >= ValidatorRulePrivate::ascii_0) &&
532  (uni <= ValidatorRulePrivate::ascii_9)) ||
533  ((uni >= ValidatorRulePrivate::ascii_A) &&
534  (uni <= ValidatorRulePrivate::ascii_Z)) ||
535  ((uni >= ValidatorRulePrivate::ascii_a) &&
536  (uni <= ValidatorRulePrivate::ascii_z)))) {
537  // NOt an RFC 5321 subdomain, but still ok by RFC 5322
538  returnStatus.push_back(ValidatorEmail::RFC5322Domain);
539  }
540 
541  parseDomain += token;
542  atomListDomain[elementCount] += token;
543  elementLen++;
544  }
545  } break;
546  //-------------------------------------------------------------
547  // Domain literal
548  //-------------------------------------------------------------
549  case ComponentLiteral:
550  {
551  // https://tools.ietf.org/html/rfc5322#section-3.4.1
552  // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
553  //
554  // dtext = %d33-90 / ; Printable US-ASCII
555  // %d94-126 / ; characters not including
556  // obs-dtext ; "[", "]", or "\"
557  //
558  // obs-dtext = obs-NO-WS-CTL / quoted-pair
559  if (token == QLatin1Char(']')) { // End of domain literal
560  if (static_cast<int>(
561  *std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) <
562  static_cast<int>(ValidatorEmail::Deprecated)) {
563  // Could be a valid RFC 5321 address literal, so let's check
564 
565  // https://tools.ietf.org/html/rfc5321#section-4.1.2
566  // address-literal = "[" ( IPv4-address-literal /
567  // IPv6-address-literal /
568  // General-address-literal ) "]"
569  // ; See Section 4.1.3
570  //
571  // https://tools.ietf.org/html/rfc5321#section-4.1.3
572  // IPv4-address-literal = Snum 3("." Snum)
573  //
574  // IPv6-address-literal = "IPv6:" IPv6-addr
575  //
576  // General-address-literal = Standardized-tag ":" 1*dcontent
577  //
578  // Standardized-tag = Ldh-str
579  // ; Standardized-tag MUST be specified in a
580  // ; Standards-Track RFC and registered with IANA
581  //
582  // dcontent = %d33-90 / ; Printable US-ASCII
583  // %d94-126 ; excl. "[", "\", "]"
584  //
585  // Snum = 1*3DIGIT
586  // ; representing a decimal integer
587  // ; value in the range 0 through 255
588  //
589  // IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
590  //
591  // IPv6-hex = 1*4HEXDIG
592  //
593  // IPv6-full = IPv6-hex 7(":" IPv6-hex)
594  //
595  // IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::"
596  // [IPv6-hex *5(":" IPv6-hex)]
597  // ; The "::" represents at least 2 16-bit groups of
598  // ; zeros. No more than 6 groups in addition to the
599  // ; "::" may be present.
600  //
601  // IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
602  //
603  // IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::"
604  // [IPv6-hex *3(":" IPv6-hex) ":"]
605  // IPv4-address-literal
606  // ; The "::" represents at least 2 16-bit groups of
607  // ; zeros. No more than 4 groups in addition to the
608  // ; "::" and IPv4-address-literal may be present.
609  //
610  // is_email() author's note: We can't use ip2long() to validate
611  // IPv4 addresses because it accepts abbreviated addresses
612  // (xxx.xxx.xxx), expanding the last group to complete the address.
613  // filter_var() validates IPv6 address inconsistently (up to PHP 5.3.3
614  // at least) -- see https://bugs.php.net/bug.php?id=53236 for example
615 
616  int maxGroups = 8; // NOLINT(cppcoreguidelines-avoid-magic-numbers)
617  qsizetype index = -1;
618  QString addressLiteral = parseLiteral;
619 
620  const QRegularExpressionMatch ipv4Match =
621  ValidatorEmailPrivate::ipv4Regex.match(addressLiteral);
622  if (ipv4Match.hasMatch()) {
623  index = addressLiteral.lastIndexOf(ipv4Match.captured());
624  if (index != 0) {
625  addressLiteral =
626  addressLiteral.mid(0, index) +
628  "0:0"); // Convert IPv4 part to IPv6 format for further testing
629  }
630  }
631 
632  if (index == 0) {
633  // Nothing there except a valid IPv4 address, so...
634  returnStatus.push_back(ValidatorEmail::RFC5321AddressLiteral);
635  } else if (QString::compare(
636  addressLiteral.left(5),
638  "IPv6:")) != // NOLINT(cppcoreguidelines-avoid-magic-numbers)
639  0) {
640  returnStatus.push_back(ValidatorEmail::RFC5322DomainLiteral);
641  } else {
642  const QString ipv6 = addressLiteral.mid(5);
643  const QStringList matchesIP = ipv6.split(QLatin1Char(':'));
644  qsizetype groupCount = matchesIP.size();
645  index = ipv6.indexOf(QLatin1String("::"));
646 
647  if (index < 0) {
648  // We need exactly the right number of groups
649  if (groupCount != maxGroups) {
650  returnStatus.push_back(ValidatorEmail::RFC5322IPv6GroupCount);
651  }
652  } else {
653  if (index != ipv6.lastIndexOf(QLatin1String("::"))) {
654  returnStatus.push_back(ValidatorEmail::RFC5322IPv62x2xColon);
655  } else {
656  if ((index == 0) || (index == (ipv6.length() - 2))) {
657  maxGroups++;
658  }
659 
660  if (groupCount > maxGroups) {
661  returnStatus.push_back(ValidatorEmail::RFC5322IPv6MaxGroups);
662  } else if (groupCount == maxGroups) {
663  returnStatus.push_back(
664  ValidatorEmail::RFC5321IPv6Deprecated); // Eliding a single
665  // "::"
666  }
667  }
668  }
669 
670  if ((ipv6.size() == 1 && ipv6[0] == QLatin1Char(':')) ||
671  (ipv6[0] == QLatin1Char(':') && ipv6[1] != QLatin1Char(':'))) {
672  returnStatus.push_back(
673  ValidatorEmail::RFC5322IPv6ColonStart); // Address starts with a
674  // single colon
675  } else if (ipv6.right(2).at(1) == QLatin1Char(':') &&
676  ipv6.right(2).at(0) != QLatin1Char(':')) {
677  returnStatus.push_back(
678  ValidatorEmail::RFC5322IPv6ColonEnd); // Address ends with a single
679  // colon
680  } else {
681  int unmatchedChars = 0;
682  for (const QString &ip : matchesIP) {
683  if (!ip.contains(ValidatorEmailPrivate::ipv6PartRegex)) {
684  unmatchedChars++;
685  }
686  }
687  if (unmatchedChars != 0) {
688  returnStatus.push_back(ValidatorEmail::RFC5322IPv6BadChar);
689  } else {
690  returnStatus.push_back(ValidatorEmail::RFC5321AddressLiteral);
691  }
692  }
693  }
694 
695  } else {
696  returnStatus.push_back(ValidatorEmail::RFC5322DomainLiteral);
697  }
698 
699  parseDomain += token;
700  atomListDomain[elementCount] += token;
701  elementLen++;
702  contextPrior = context;
703  context = contextStack.takeLast();
704  } else if (token == QLatin1Char('\\')) {
705  returnStatus.push_back(ValidatorEmail::RFC5322DomLitOBSDText);
706  contextStack.push_back(context);
707  context = ContextQuotedPair;
708  } else if ((token == QChar(QChar::CarriageReturn)) || (token == QChar(QChar::Space)) ||
709  (token == QChar(QChar::Tabulation))) { // Folding White Space
710  if ((token == QChar(QChar::CarriageReturn)) &&
711  ((++i == rawLength) || (email[i] != QChar(QChar::LineFeed)))) {
712  returnStatus.push_back(ValidatorEmail::ErrorCRnoLF); // Fatal error
713  break;
714  }
715 
716  returnStatus.push_back(ValidatorEmail::CFWSFWS);
717  contextStack.push_back(context);
718  context = ContextFWS;
719  tokenPrior = token;
720 
721  } else { // dtext
722  // https://tools.ietf.org/html/rfc5322#section-3.4.1
723  // dtext = %d33-90 / ; Printable US-ASCII
724  // %d94-126 / ; characters not including
725  // obs-dtext ; "[", "]", or "\"
726  //
727  // obs-dtext = obs-NO-WS-CTL / quoted-pair
728  //
729  // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
730  // %d11 / ; characters that do not
731  // %d12 / ; include the carriage
732  // %d14-31 / ; return, line feed, and
733  // %d127 ; white space characters
734  const char16_t uni = token.unicode();
735 
736  // CR, LF, SP & HTAB have already been parsed above
737  if ((uni > ValidatorEmailPrivate::asciiEnd) || (uni == 0) ||
738  (uni == QLatin1Char('[').unicode())) {
739  returnStatus.push_back(ValidatorEmail::ErrorExpectingDText); // Fatal error
740  break;
741  } else if ((uni < ValidatorEmailPrivate::asciiExclamationMark) ||
742  (uni == ValidatorEmailPrivate::asciiEnd)) {
743  returnStatus.push_back(ValidatorEmail::RFC5322DomLitOBSDText);
744  }
745 
746  parseLiteral += token;
747  parseDomain += token;
748  atomListDomain[elementCount] += token;
749  elementLen++;
750  }
751  } break;
752  //-------------------------------------------------------------
753  // Quoted string
754  //-------------------------------------------------------------
755  case ContextQuotedString:
756  {
757  // https://tools.ietf.org/html/rfc5322#section-3.2.4
758  // quoted-string = [CFWS]
759  // DQUOTE *([FWS] qcontent) [FWS] DQUOTE
760  // [CFWS]
761  //
762  // qcontent = qtext / quoted-pair
763  if (token == QLatin1Char('\\')) { // Quoted pair
764  contextStack.push_back(context);
765  context = ContextQuotedPair;
766  } else if ((token == QChar(QChar::CarriageReturn)) ||
767  (token == QChar(QChar::Tabulation))) { // Folding White Space
768  // Inside a quoted string, spaces are allowed as regular characters.
769  // It's only FWS if we include HTAB or CRLF
770  if ((token == QChar(QChar::CarriageReturn)) &&
771  ((++i == rawLength) || (email[i] != QChar(QChar::LineFeed)))) {
772  returnStatus.push_back(ValidatorEmail::ErrorCRnoLF);
773  break;
774  }
775 
776  // https://tools.ietf.org/html/rfc5322#section-3.2.2
777  // Runs of FWS, comment, or CFWS that occur between lexical tokens in a
778  // structured header field are semantically interpreted as a single
779  // space character.
780 
781  // https://tools.ietf.org/html/rfc5322#section-3.2.4
782  // the CRLF in any FWS/CFWS that appears within the quoted-string [is]
783  // semantically "invisible" and therefore not part of the quoted-string
784 
785  parseLocalPart += QChar(QChar::Space);
786  atomListLocalPart[elementCount] += QChar(QChar::Space);
787  elementLen++;
788 
789  returnStatus.push_back(ValidatorEmail::CFWSFWS);
790  contextStack.push_back(context);
791  context = ContextFWS;
792  tokenPrior = token;
793  } else if (token == QLatin1Char('"')) { // end of quoted string
794  parseLocalPart += token;
795  atomListLocalPart[elementCount] += token;
796  elementLen++;
797  contextPrior = context;
798  context = contextStack.takeLast();
799  } else { // qtext
800  // https://tools.ietf.org/html/rfc5322#section-3.2.4
801  // qtext = %d33 / ; Printable US-ASCII
802  // %d35-91 / ; characters not including
803  // %d93-126 / ; "\" or the quote character
804  // obs-qtext
805  //
806  // obs-qtext = obs-NO-WS-CTL
807  //
808  // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
809  // %d11 / ; characters that do not
810  // %d12 / ; include the carriage
811  // %d14-31 / ; return, line feed, and
812  // %d127 ; white space characters
813  const char16_t uni = token.unicode();
814 
815  if (!allowUtf8Local) {
816  if ((uni > ValidatorEmailPrivate::asciiEnd) || (uni == 0) ||
817  (uni == ValidatorEmailPrivate::asciiLF)) {
818  returnStatus.push_back(ValidatorEmail::ErrorExpectingQText); // Fatal error
819  } else if ((uni < ValidatorRulePrivate::asciiSpace) ||
820  (uni == ValidatorEmailPrivate::asciiEnd)) {
821  returnStatus.push_back(ValidatorEmail::DeprecatedQText);
822  }
823  } else {
824  if (!token.isLetterOrNumber()) {
825  if ((uni > ValidatorEmailPrivate::asciiEnd) || (uni == 0) ||
826  (uni == ValidatorEmailPrivate::asciiLF)) {
827  returnStatus.push_back(
828  ValidatorEmail::ErrorExpectingQText); // Fatal error
829  } else if ((uni < ValidatorRulePrivate::asciiSpace) ||
830  (uni == ValidatorEmailPrivate::asciiEnd)) {
831  returnStatus.push_back(ValidatorEmail::DeprecatedQText);
832  }
833  }
834  }
835 
836  parseLocalPart += token;
837  atomListLocalPart[elementCount] += token;
838  elementLen++;
839  }
840 
841  // https://tools.ietf.org/html/rfc5322#section-3.4.1
842  // If the
843  // string can be represented as a dot-atom (that is, it contains no
844  // characters other than atext characters or "." surrounded by atext
845  // characters), then the dot-atom form SHOULD be used and the quoted-
846  // string form SHOULD NOT be used.
847  // To do
848  } break;
849  //-------------------------------------------------------------
850  // Quoted pair
851  //-------------------------------------------------------------
852  case ContextQuotedPair:
853  {
854  // https://tools.ietf.org/html/rfc5322#section-3.2.1
855  // quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
856  //
857  // VCHAR = %d33-126 ; visible (printing) characters
858  // WSP = SP / HTAB ; white space
859  //
860  // obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
861  //
862  // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
863  // %d11 / ; characters that do not
864  // %d12 / ; include the carriage
865  // %d14-31 / ; return, line feed, and
866  // %d127 ; white space characters
867  //
868  // i.e. obs-qp = "\" (%d0-8, %d10-31 / %d127)
869 
870  const char16_t uni = token.unicode();
871 
872  if (uni > ValidatorEmailPrivate::asciiEnd) {
873  returnStatus.push_back(ValidatorEmail::ErrorExpectingQpair); // Fatal error
874  } else if (((uni < ValidatorEmailPrivate::asciiUS) &&
875  (uni != ValidatorRulePrivate::asciiTab)) ||
876  (uni == ValidatorEmailPrivate::asciiEnd)) {
877  returnStatus.push_back(ValidatorEmail::DeprecatedQP);
878  }
879 
880  // At this point we know where this qpair occurred so
881  // we could check to see if the character actually
882  // needed to be quoted at all.
883  // https://tools.ietf.org/html/rfc5321#section-4.1.2
884  // the sending system SHOULD transmit the
885  // form that uses the minimum quoting possible.
886 
887  contextPrior = context;
888  context = contextStack.takeLast();
889 
890  switch (context) {
891  case ContextComment:
892  break;
893  case ContextQuotedString:
894  parseLocalPart += QLatin1Char('\\');
895  parseLocalPart += token;
896  atomListLocalPart[elementCount] += QLatin1Char('\\');
897  atomListLocalPart[elementCount] += token;
898  elementLen += 2; // The maximum sizes specified by RFC 5321 are octet counts, so we
899  // must include the backslash
900  break;
901  case ComponentLiteral:
902  parseDomain += QLatin1Char('\\');
903  parseDomain += token;
904  atomListDomain[elementCount] += QLatin1Char('\\');
905  atomListDomain[elementCount] += token;
906  elementLen += 2; // The maximum sizes specified by RFC 5321 are octet counts, so we
907  // must include the backslash
908  break;
909  default:
910  returnStatus.push_back(ValidatorEmail::ErrorFatal);
911  qCCritical(C_VALIDATOR)
912  << "ValidatorEmail: Quoted pair logic invoked in an invalid context.";
913  break;
914  }
915  } break;
916  //-------------------------------------------------------------
917  // Comment
918  //-------------------------------------------------------------
919  case ContextComment:
920  {
921  // https://tools.ietf.org/html/rfc5322#section-3.2.2
922  // comment = "(" *([FWS] ccontent) [FWS] ")"
923  //
924  // ccontent = ctext / quoted-pair / comment
925  if (token == QLatin1Char('(')) { // netsted comment
926  // nested comments are OK
927  contextStack.push_back(context);
928  context = ContextComment;
929  } else if (token == QLatin1Char(')')) {
930  contextPrior = context;
931  context = contextStack.takeLast();
932 
933  // https://tools.ietf.org/html/rfc5322#section-3.2.2
934  // Runs of FWS, comment, or CFWS that occur between lexical tokens in a
935  // structured header field are semantically interpreted as a single
936  // space character.
937  //
938  // is_email() author's note: This *cannot* mean that we must add a
939  // space to the address wherever CFWS appears. This would result in
940  // any addr-spec that had CFWS outside a quoted string being invalid
941  // for RFC 5321.
942  // if (($context === ISEMAIL_COMPONENT_LOCALPART) ||
943  //($context === ISEMAIL_COMPONENT_DOMAIN)) {
944  // $parsedata[$context] .=
945  // ISEMAIL_STRING_SP;
946  // $atomlist[$context][$element_count]
947  // .= ISEMAIL_STRING_SP; $element_len++;
948  // }
949  } else if (token == QLatin1Char('\\')) { // Quoted pair
950  contextStack.push_back(context);
951  context = ContextQuotedPair;
952  } else if ((token == QChar(QChar::CarriageReturn)) || (token == QChar(QChar::Space)) ||
953  (token == QChar(QChar::Tabulation))) { // Folding White Space
954  if ((token == QChar(QChar::CarriageReturn)) &&
955  ((++i == rawLength) || (email[i] != QChar(QChar::LineFeed)))) {
956  returnStatus.push_back(ValidatorEmail::ErrorCRnoLF);
957  break;
958  }
959 
960  returnStatus.push_back(ValidatorEmail::CFWSFWS);
961  contextStack.push_back(context);
962  context = ContextFWS;
963  tokenPrior = token;
964  } else { // ctext
965  // https://tools.ietf.org/html/rfc5322#section-3.2.3
966  // ctext = %d33-39 / ; Printable US-ASCII
967  // %d42-91 / ; characters not including
968  // %d93-126 / ; "(", ")", or "\"
969  // obs-ctext
970  //
971  // obs-ctext = obs-NO-WS-CTL
972  //
973  // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
974  // %d11 / ; characters that do not
975  // %d12 / ; include the carriage
976  // %d14-31 / ; return, line feed, and
977  // %d127 ; white space characters
978 
979  const ushort uni = token.unicode();
980 
981  if ((uni > ValidatorEmailPrivate::asciiEnd) || (uni == 0) ||
982  (uni == ValidatorEmailPrivate::asciiLF)) {
983  returnStatus.push_back(ValidatorEmail::ErrorExpectingCText); // Fatal error
984  break;
985  } else if ((uni < ValidatorRulePrivate::asciiSpace) ||
986  (uni == ValidatorEmailPrivate::asciiEnd)) {
987  returnStatus.push_back(ValidatorEmail::DeprecatedCText);
988  }
989  }
990  } break;
991  //-------------------------------------------------------------
992  // Folding White Space
993  //-------------------------------------------------------------
994  case ContextFWS:
995  {
996  // https://tools.ietf.org/html/rfc5322#section-3.2.2
997  // FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
998  // ; Folding white space
999  // But note the erratum:
1000  // https://www.rfc-editor.org/errata_search.php?rfc=5322&eid=1908:
1001  // In the obsolete syntax, any amount of folding white space MAY be
1002  // inserted where the obs-FWS rule is allowed. This creates the
1003  // possibility of having two consecutive "folds" in a line, and
1004  // therefore the possibility that a line which makes up a folded header
1005  // field could be composed entirely of white space.
1006  //
1007  // obs-FWS = 1*([CRLF] WSP)
1008  if (tokenPrior == QChar(QChar::CarriageReturn)) {
1009  if (token == QChar(QChar::CarriageReturn)) {
1010  returnStatus.push_back(ValidatorEmail::ErrorFWSCRLFx2); // Fatal error
1011  break;
1012  }
1013 
1014  if (crlf_count > 0) {
1015  if (++crlf_count > 1) {
1016  returnStatus.push_back(
1017  ValidatorEmail::DeprecatedFWS); // Multiple folds = obsolete FWS
1018  }
1019  } else {
1020  crlf_count = 1;
1021  }
1022  }
1023 
1024  if (token == QChar(QChar::CarriageReturn)) {
1025  if ((++i == rawLength) || (email[i] != QChar(QChar::LineFeed))) {
1026  returnStatus.push_back(ValidatorEmail::ErrorCRnoLF);
1027  break;
1028  }
1029  } else if ((token != QChar(QChar::Space)) && (token != QChar(QChar::Tabulation))) {
1030  if (tokenPrior == QChar(QChar::CarriageReturn)) {
1031  returnStatus.push_back(ValidatorEmail::ErrorFWSCRLFEnd); // Fatal error
1032  break;
1033  }
1034 
1035  if (crlf_count > 0) {
1036  crlf_count = 0;
1037  }
1038 
1039  contextPrior = context;
1040  context = contextStack.takeLast(); // End of FWS
1041 
1042  // https://tools.ietf.org/html/rfc5322#section-3.2.2
1043  // Runs of FWS, comment, or CFWS that occur between lexical tokens in a
1044  // structured header field are semantically interpreted as a single
1045  // space character.
1046  //
1047  // is_email() author's note: This *cannot* mean that we must add a
1048  // space to the address wherever CFWS appears. This would result in
1049  // any addr-spec that had CFWS outside a quoted string being invalid
1050  // for RFC 5321.
1051  // if (($context === ISEMAIL_COMPONENT_LOCALPART) ||
1052  //($context === ISEMAIL_COMPONENT_DOMAIN)) {
1053  // $parsedata[$context] .=
1054  // ISEMAIL_STRING_SP;
1055  // $atomlist[$context][$element_count]
1056  // .= ISEMAIL_STRING_SP; $element_len++;
1057  // }
1058 
1059  i--; // Look at this token again in the parent context
1060  }
1061 
1062  tokenPrior = token;
1063  } break;
1064  default:
1065  returnStatus.push_back(ValidatorEmail::ErrorFatal);
1066  qCCritical(C_VALIDATOR) << "ValidatorEmail: Unknown context";
1067  break;
1068  }
1069 
1070  if (static_cast<int>(
1071  *std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) >
1072  static_cast<int>(ValidatorEmail::RFC5322)) {
1073  break;
1074  }
1075  }
1076 
1077  // Some simple final tests
1078  if (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) <
1079  static_cast<int>(ValidatorEmail::RFC5322)) {
1080  if (context == ContextQuotedString) {
1081  returnStatus.push_back(ValidatorEmail::ErrorUnclosedQuotedStr);
1082  } else if (context == ContextQuotedPair) {
1083  returnStatus.push_back(ValidatorEmail::ErrorBackslashEnd);
1084  } else if (context == ContextComment) {
1085  returnStatus.push_back(ValidatorEmail::ErrorUnclosedComment);
1086  } else if (context == ComponentLiteral) {
1087  returnStatus.push_back(ValidatorEmail::ErrorUnclosedDomLiteral);
1088  } else if (token == QChar(QChar::CarriageReturn)) {
1089  returnStatus.push_back(ValidatorEmail::ErrorFWSCRLFEnd);
1090  } else if (parseDomain.isEmpty()) {
1091  returnStatus.push_back(ValidatorEmail::ErrorNoDomain);
1092  } else if (elementLen == 0) {
1093  returnStatus.push_back(ValidatorEmail::ErrorDotEnd);
1094  } else if (hypenFlag) {
1095  returnStatus.push_back(ValidatorEmail::ErrorDomainHyphenEnd);
1096  } else if (parseDomain.size() > ValidatorEmailPrivate::maxDomainLength) {
1097  // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.2
1098  // The maximum total length of a domain name or number is 255 octets.
1099  returnStatus.push_back(ValidatorEmail::RFC5322DomainTooLong);
1100  } else if ((parseLocalPart.size() + 1 + parseDomain.size()) >
1101  ValidatorEmailPrivate::maxMailboxLength) {
1102  // https://tools.ietf.org/html/rfc5321#section-4.1.2
1103  // Forward-path = Path
1104  //
1105  // Path = "<" [ A-d-l ":" ] Mailbox ">"
1106  //
1107  // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
1108  // The maximum total length of a reverse-path or forward-path is 256
1109  // octets (including the punctuation and element separators).
1110  //
1111  // Thus, even without (obsolete) routing information, the Mailbox can
1112  // only be 254 characters long. This is confirmed by this verified
1113  // erratum to RFC 3696:
1114  //
1115  // https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
1116  // However, there is a restriction in RFC 2821 on the length of an
1117  // address in MAIL and RCPT commands of 254 characters. Since addresses
1118  // that do not fit in those fields are not normally useful, the upper
1119  // limit on address lengths should normally be considered to be 254.
1120  returnStatus.push_back(ValidatorEmail::RFC5322TooLong);
1121  } else if (elementLen > ValidatorEmailPrivate::maxDnsLabelLength) {
1122  returnStatus.push_back(ValidatorEmail::RFC5322LabelTooLong);
1123  }
1124  }
1125 
1126  // Check DNS?
1127  bool dnsChecked = false;
1128 
1129  if (checkDns &&
1130  (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) <
1131  static_cast<int>(threshold))) {
1132  // https://tools.ietf.org/html/rfc5321#section-2.3.5
1133  // Names that can
1134  // be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
1135  // in Section 5) are permitted, as are CNAME RRs whose targets can be
1136  // resolved, in turn, to MX or address RRs.
1137  //
1138  // https://tools.ietf.org/html/rfc5321#section-5.1
1139  // The lookup first attempts to locate an MX record associated with the
1140  // name. If a CNAME record is found, the resulting name is processed as
1141  // if it were the initial name. ... If an empty list of MXs is returned,
1142  // the address is treated as if it was associated with an implicit MX
1143  // RR, with a preference of 0, pointing to that host.
1144 
1145  if (elementCount == 0) {
1146  parseDomain += QLatin1Char('.');
1147  }
1148 
1149  QDnsLookup mxLookup(QDnsLookup::MX, parseDomain);
1150  QEventLoop mxLoop;
1151  QObject::connect(&mxLookup, &QDnsLookup::finished, &mxLoop, &QEventLoop::quit);
1152  QTimer::singleShot(ValidatorEmailPrivate::dnsLookupTimeout, &mxLookup, &QDnsLookup::abort);
1153  mxLookup.lookup();
1154  mxLoop.exec();
1155 
1156  if ((mxLookup.error() == QDnsLookup::NoError) && !mxLookup.mailExchangeRecords().empty()) {
1157  dnsChecked = true;
1158  } else {
1159  returnStatus.push_back(ValidatorEmail::DnsWarnNoMxRecord);
1160  QDnsLookup aLookup(QDnsLookup::A, parseDomain);
1161  QEventLoop aLoop;
1164  ValidatorEmailPrivate::dnsLookupTimeout, &aLookup, &QDnsLookup::abort);
1165  aLookup.lookup();
1166  aLoop.exec();
1167 
1168  if ((aLookup.error() == QDnsLookup::NoError) && !aLookup.hostAddressRecords().empty()) {
1169  dnsChecked = true;
1170  } else {
1171  returnStatus.push_back(ValidatorEmail::DnsWarnNoRecord);
1172  }
1173  }
1174  }
1175 
1176  // Check for TLD addresses
1177  // -----------------------
1178  // TLD addresses are specifically allowed in RFC 5321 but they are
1179  // unusual to say the least. We will allocate a separate
1180  // status to these addresses on the basis that they are more likely
1181  // to be typos than genuine addresses (unless we've already
1182  // established that the domain does have an MX record)
1183  //
1184  // https://tools.ietf.org/html/rfc5321#section-2.3.5
1185  // In the case
1186  // of a top-level domain used by itself in an email address, a single
1187  // string is used without any dots. This makes the requirement,
1188  // described in more detail below, that only fully-qualified domain
1189  // names appear in SMTP transactions on the public Internet,
1190  // particularly important where top-level domains are involved.
1191  //
1192  // TLD format
1193  // ----------
1194  // The format of TLDs has changed a number of times. The standards
1195  // used by IANA have been largely ignored by ICANN, leading to
1196  // confusion over the standards being followed. These are not defined
1197  // anywhere, except as a general component of a DNS host name (a label).
1198  // However, this could potentially lead to 123.123.123.123 being a
1199  // valid DNS name (rather than an IP address) and thereby creating
1200  // an ambiguity. The most authoritative statement on TLD formats that
1201  // the author can find is in a (rejected!) erratum to RFC 1123
1202  // submitted by John Klensin, the author of RFC 5321:
1203  //
1204  // https://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
1205  // However, a valid host name can never have the dotted-decimal
1206  // form #.#.#.#, since this change does not permit the highest-level
1207  // component label to start with a digit even if it is not all-numeric.
1208  if (!dnsChecked &&
1209  (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) <
1210  static_cast<int>(ValidatorEmail::DNSWarn))) {
1211  if (elementCount == 0) {
1212  returnStatus.push_back(ValidatorEmail::RFC5321TLD);
1213  }
1214 
1215  if (QStringLiteral("0123456789").contains(atomListDomain[elementCount][0])) {
1216  returnStatus.push_back(ValidatorEmail::RFC5321TLDNumeric);
1217  }
1218  }
1219 
1220  if (returnStatus.size() != 1) {
1222  for (const ValidatorEmail::Diagnose dia : std::as_const(returnStatus)) {
1223  if (!_rs.contains(dia) && (dia != ValidatorEmail::ValidAddress)) {
1224  _rs.append(dia); // clazy:exclude=reserve-candidates
1225  }
1226  }
1227  returnStatus = _rs;
1228 
1229  std::sort(returnStatus.begin(), returnStatus.end(), std::greater<>());
1230  }
1231 
1232  const ValidatorEmail::Diagnose finalStatus = returnStatus.at(0);
1233 
1234  if (diagnoseStruct) {
1235  diagnoseStruct->finalStatus = finalStatus;
1236  diagnoseStruct->returnStatus = returnStatus;
1237  diagnoseStruct->localpart = parseLocalPart;
1238  diagnoseStruct->domain = parseDomain;
1239  diagnoseStruct->literal = parseLiteral;
1240  }
1241 
1242  return static_cast<int>(finalStatus) < static_cast<int>(threshold);
1243 }
1244 
1246 {
1247  if (label.isEmpty()) {
1248  switch (diagnose) {
1249  case ValidAddress:
1250  //% "Address is valid. Please note that this does not mean that both the "
1251  //% "address and the domain actually exist. This address could be issued "
1252  //% "by the domain owner without breaking the rules of any RFCs."
1253  return c->qtTrId("cutelyst-valemail-diag-valid");
1254  case DnsWarnNoMxRecord:
1255  //% "Could not find an MX record for this address’ domain but an A record exists."
1256  return c->qtTrId("cutelyst-valemail-diag-nomx");
1257  case DnsWarnNoRecord:
1258  //% "Could neither find an MX record nor an A record for this address’ domain."
1259  return c->qtTrId("cutelyst-valemail-diag-noarec");
1260  case RFC5321TLD:
1261  //% "Address is valid but at a Top Level Domain."
1262  return c->qtTrId("cutelyst-valemail-diag-rfc5321tld");
1263  case RFC5321TLDNumeric:
1264  //% "Address is valid but the Top Level Domain begins with a number."
1265  return c->qtTrId("cutelyst-valemail-diag-rfc5321tldnumeric");
1266  case RFC5321QuotedString:
1267  //% "Address is valid but contains a quoted string."
1268  return c->qtTrId("cutelyst-valemail-diag-rfc5321quotedstring");
1269  case RFC5321AddressLiteral:
1270  //% "Address is valid but uses an IP address instead of a domain name."
1271  return c->qtTrId("cutelyst-valemail-diag-rfc5321addressliteral");
1272  case RFC5321IPv6Deprecated:
1273  //% "Address is valid but uses an IP address that contains a :: only "
1274  //% "eliding one zero group. All implementations must accept and be "
1275  //% "able to handle any legitimate RFC 4291 format."
1276  return c->qtTrId("cutelyst-valemail-diag-rfc5321ipv6deprecated");
1277  case CFWSComment:
1278  //% "Address contains comments."
1279  return c->qtTrId("cutelyst-valemail-diag-cfwscomment");
1280  case CFWSFWS:
1281  //% "Address contains folding white spaces like line breaks."
1282  return c->qtTrId("cutelyst-valemail-diag-cfwsfws");
1283  case DeprecatedLocalpart:
1284  //% "The local part is in a deprecated form."
1285  return c->qtTrId("cutelyst-valemail-diag-deprecatedlocalpart");
1286  case DeprecatedFWS:
1287  //% "Address contains an obsolete form of folding white spaces."
1288  return c->qtTrId("cutelyst-valemail-diag-deprecatedfws");
1289  case DeprecatedQText:
1290  //% "A quoted string contains a deprecated character."
1291  return c->qtTrId("cutelyst-valemail-diag-deprecatedqtext");
1292  case DeprecatedQP:
1293  //% "A quoted pair contains a deprecated character."
1294  return c->qtTrId("cutelyst-valemail-diag-deprecatedqp");
1295  case DeprecatedComment:
1296  //% "Address contains a comment in a position that is deprecated."
1297  return c->qtTrId("cutelyst-valemail-diag-deprecatedcomment");
1298  case DeprecatedCText:
1299  //% "A comment contains a deprecated character."
1300  return c->qtTrId("cutelyst-valemail-diag-deprecatedctext");
1301  case DeprecatedCFWSNearAt:
1302  //% "Address contains a comment or folding white space around the @ sign."
1303  return c->qtTrId("cutelyst-valemail-diag-cfwsnearat");
1304  case RFC5322Domain:
1305  //% "Address is RFC 5322 compliant but contains domain characters that "
1306  //% "are not allowed by DNS."
1307  return c->qtTrId("cutelyst-valemail-diag-rfc5322domain");
1308  case RFC5322TooLong:
1309  //% "The address exceeds the maximum allowed length of %1 characters."
1310  return c->qtTrId("cutelyst-valemail-diag-rfc5322toolong")
1311  .arg(c->locale().toString(ValidatorEmailPrivate::maxMailboxLength));
1312  case RFC5322LocalTooLong:
1313  //% "The local part of the address exceeds the maximum allowed length "
1314  //% "of %1 characters."
1315  return c->qtTrId("cutelyst-valemail-diag-rfc5322localtoolong")
1316  .arg(c->locale().toString(ValidatorEmailPrivate::maxLocalPartLength));
1317  case RFC5322DomainTooLong:
1318  //% "The domain part exceeds the maximum allowed length of %1 characters."
1319  return c->qtTrId("cutelyst-valemail-diag-rfc5322domaintoolong")
1320  .arg(c->locale().toString(ValidatorEmailPrivate::maxDomainLength));
1321  case RFC5322LabelTooLong:
1322  //% "One of the labels/sections in the domain part exceeds the maximum allowed "
1323  //% "length of %1 characters."
1324  return c->qtTrId("cutelyst-valemail-diag-rfc5322labeltoolong")
1325  .arg(c->locale().toString(ValidatorEmailPrivate::maxDnsLabelLength));
1326  case RFC5322DomainLiteral:
1327  //% "The domain literal is not a valid RFC 5321 address literal."
1328  return c->qtTrId("cutelyst-valemail-diag-rfc5322domainliteral");
1329  case RFC5322DomLitOBSDText:
1330  //% "The domain literal is not a valid RFC 5321 domain literal and it "
1331  //% "contains obsolete characters."
1332  return c->qtTrId("cutelyst-valemail-diag-rfc5322domlitobsdtext");
1333  case RFC5322IPv6GroupCount:
1334  //% "The IPv6 literal address contains the wrong number of groups."
1335  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6groupcount");
1336  case RFC5322IPv62x2xColon:
1337  //% "The IPv6 literal address contains too many :: sequences."
1338  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv62x2xcolon");
1339  case RFC5322IPv6BadChar:
1340  //% "The IPv6 address contains an illegal group of characters."
1341  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6badchar");
1342  case RFC5322IPv6MaxGroups:
1343  //% "The IPv6 address has too many groups."
1344  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6maxgroups");
1345  case RFC5322IPv6ColonStart:
1346  //% "The IPv6 address starts with a single colon."
1347  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonstart");
1348  case RFC5322IPv6ColonEnd:
1349  //% "The IPv6 address ends with a single colon."
1350  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonend");
1351  case ErrorExpectingDText:
1352  //% "A domain literal contains a character that is not allowed."
1353  return c->qtTrId("cutelyst-valemail-diag-errexpectingdtext");
1354  case ErrorNoLocalPart:
1355  //% "Address has no local part."
1356  return c->qtTrId("cutelyst-valemail-diag-errnolocalpart");
1357  case ErrorNoDomain:
1358  //% "Address has no domain part."
1359  return c->qtTrId("cutelyst-valemail-diag-errnodomain");
1360  case ErrorConsecutiveDots:
1361  //% "The address must not contain consecutive dots."
1362  return c->qtTrId("cutelyst-valemail-diag-errconsecutivedots");
1363  case ErrorATextAfterCFWS:
1364  //% "Address contains text after a comment or folding white space."
1365  return c->qtTrId("cutelyst-valemail-diag-erratextaftercfws");
1366  case ErrorATextAfterQS:
1367  //% "Address contains text after a quoted string."
1368  return c->qtTrId("cutelyst-valemail-diag-erratextafterqs");
1369  case ErrorATextAfterDomLit:
1370  //% "Extra characters were found after the end of the domain literal."
1371  return c->qtTrId("cutelyst-valemail-diag-erratextafterdomlit");
1372  case ErrorExpectingQpair:
1373  //% "The Address contains a character that is not allowed in a quoted pair."
1374  return c->qtTrId("cutelyst-valemail-diag-errexpectingqpair");
1375  case ErrorExpectingAText:
1376  //% "Address contains a character that is not allowed."
1377  return c->qtTrId("cutelyst-valemail-diag-errexpectingatext");
1378  case ErrorExpectingQText:
1379  //% "A quoted string contains a character that is not allowed."
1380  return c->qtTrId("cutelyst-valemail-diag-errexpectingqtext");
1381  case ErrorExpectingCText:
1382  //% "A comment contains a character that is not allowed."
1383  return c->qtTrId("cutelyst-valemail-diag-errexpectingctext");
1384  case ErrorBackslashEnd:
1385  //% "The address can not end with a backslash."
1386  return c->qtTrId("cutelyst-valemail-diag-errbackslashend");
1387  case ErrorDotStart:
1388  //% "Neither part of the address may begin with a dot."
1389  return c->qtTrId("cutelyst-valemail-diag-errdotstart");
1390  case ErrorDotEnd:
1391  //% "Neither part of the address may end with a dot."
1392  return c->qtTrId("cutelyst-valemail-diag-errdotend");
1394  //% "A domain or subdomain can not begin with a hyphen."
1395  return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenstart");
1396  case ErrorDomainHyphenEnd:
1397  //% "A domain or subdomain can not end with a hyphen."
1398  return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenend");
1400  //% "Unclosed quoted string. (Missing double quotation mark)"
1401  return c->qtTrId("cutelyst-valemail-diag-errunclosedquotedstr");
1402  case ErrorUnclosedComment:
1403  //% "Unclosed comment. (Missing closing parentheses)"
1404  return c->qtTrId("cutelyst-valemail-diag-errunclosedcomment");
1406  //% "Domain literal is missing its closing bracket."
1407  return c->qtTrId("cutelyst-valemail-diag-erruncloseddomliteral");
1408  case ErrorFWSCRLFx2:
1409  //% "Folding white space contains consecutive line break sequences (CRLF)."
1410  return c->qtTrId("cutelyst-valemail-diag-errfwscrlfx2");
1411  case ErrorFWSCRLFEnd:
1412  //% "Folding white space ends with a line break sequence (CRLF)."
1413  return c->qtTrId("cutelyst-valemail-diag-errfwscrlfend");
1414  case ErrorCRnoLF:
1415  //% "Address contains a carriage return (CR) that is not followed by a "
1416  //% "line feed (LF)."
1417  return c->qtTrId("cutelyst-valemail-diag-errcrnolf");
1418  case ErrorFatal:
1419  //% "A fatal error occurred while parsing the address."
1420  return c->qtTrId("cutelyst-valemail-diag-errfatal");
1421  default:
1422  return {};
1423  }
1424 
1425  } else {
1426 
1427  switch (diagnose) {
1428  case ValidAddress:
1429  //% "The address in the “%1” field is valid. Please note that this does not mean "
1430  //% "that both the address and the domain actually exist. This address could be "
1431  //% "issued by the domain owner without breaking the rules of any RFCs."
1432  return c->qtTrId("cutelyst-valemail-diag-valid-label").arg(label);
1433  case DnsWarnNoMxRecord:
1434  //% "Could not find an MX record for the address’ domain in the “%1” "
1435  //% "field but an A record exists."
1436  return c->qtTrId("cutelyst-valemail-diag-nomx-label").arg(label);
1437  case DnsWarnNoRecord:
1438  //% "Could neither find an MX record nor an A record for the address’ "
1439  //% "domain in the “%1” field."
1440  return c->qtTrId("cutelyst-valemail-diag-noarec-label").arg(label);
1441  case RFC5321TLD:
1442  //% "The address in the “%1” field is valid but at a Top Level Domain."
1443  return c->qtTrId("cutelyst-valemail-diag-rfc5321tld-label").arg(label);
1444  case RFC5321TLDNumeric:
1445  //% "The address in the “%1” field is valid but the Top Level Domain "
1446  //% "begins with a number."
1447  return c->qtTrId("cutelyst-valemail-diag-rfc5321tldnumeric-label").arg(label);
1448  case RFC5321QuotedString:
1449  //% "The address in the “%1” field is valid but contains a quoted string."
1450  return c->qtTrId("cutelyst-valemail-diag-rfc5321quotedstring-label").arg(label);
1451  case RFC5321AddressLiteral:
1452  //% "The address in the “%1” field is valid but uses an IP address "
1453  //% "instead of a domain name."
1454  return c->qtTrId("cutelyst-valemail-diag-rfc5321addressliteral-label").arg(label);
1455  case RFC5321IPv6Deprecated:
1456  //% "The address in the “%1” field is valid but uses an IP address that "
1457  //% "contains a :: only eliding one zero group. All implementations "
1458  //% "must accept and be able to handle any legitimate RFC 4291 format."
1459  return c->qtTrId("cutelyst-valemail-diag-rfc5321ipv6deprecated-label").arg(label);
1460  case CFWSComment:
1461  //% "The address in the “%1” field contains comments."
1462  return c->qtTrId("cutelyst-valemail-diag-cfwscomment-label").arg(label);
1463  case CFWSFWS:
1464  //% "The address in the “%1” field contains folding white spaces like "
1465  //% "line breaks."
1466  return c->qtTrId("cutelyst-valemail-diag-cfwsfws-label").arg(label);
1467  case DeprecatedLocalpart:
1468  //% "The local part of the address in the “%1” field is in a deprecated form."
1469  return c->qtTrId("cutelyst-valemail-diag-deprecatedlocalpart-label").arg(label);
1470  case DeprecatedFWS:
1471  //% "The address in the “%1” field contains an obsolete form of folding "
1472  //% "white spaces."
1473  return c->qtTrId("cutelyst-valemail-diag-deprecatedfws-label").arg(label);
1474  case DeprecatedQText:
1475  //% "A quoted string in the address in the “%1” field contains a "
1476  //% "deprecated character."
1477  return c->qtTrId("cutelyst-valemail-diag-deprecatedqtext-label").arg(label);
1478  case DeprecatedQP:
1479  //% "A quoted pair in the address in the “%1” field contains a "
1480  //% "deprecate character."
1481  return c->qtTrId("cutelyst-valemail-diag-deprecatedqp-label").arg(label);
1482  case DeprecatedComment:
1483  //% "The address in the “%1” field contains a comment in a position "
1484  //% "that is deprecated."
1485  return c->qtTrId("cutelyst-valemail-diag-deprecatedcomment-label").arg(label);
1486  case DeprecatedCText:
1487  //% "A comment in the address in the “%1” field contains a deprecated character."
1488  return c->qtTrId("cutelyst-valemail-diag-deprecatedctext-label").arg(label);
1489  case DeprecatedCFWSNearAt:
1490  //% "The address in the “%1” field contains a comment or folding white "
1491  //% "space around the @ sign."
1492  return c->qtTrId("cutelyst-valemail-diag-cfwsnearat-label").arg(label);
1493  case RFC5322Domain:
1494  //% "The address in the “%1” field is RFC 5322 compliant but contains "
1495  //% "domain characters that are not allowed by DNS."
1496  return c->qtTrId("cutelyst-valemail-diag-rfc5322domain-label").arg(label);
1497  case RFC5322TooLong:
1498  //% "The address in the “%1” field exceeds the maximum allowed length "
1499  //% "of %2 characters."
1500  return c->qtTrId("cutelyst-valemail-diag-rfc5322toolong-label")
1501  .arg(label, c->locale().toString(ValidatorEmailPrivate::maxMailboxLength));
1502  case RFC5322LocalTooLong:
1503  //% "The local part of the address in the “%1” field exceeds the maximum allowed "
1504  //% "length of %2 characters."
1505  return c->qtTrId("cutelyst-valemail-diag-rfc5322localtoolong-label")
1506  .arg(label, c->locale().toString(ValidatorEmailPrivate::maxLocalPartLength));
1507  case RFC5322DomainTooLong:
1508  //% "The domain part of the address in the “%1” field exceeds the maximum "
1509  //% "allowed length of %2 characters."
1510  return c->qtTrId("cutelyst-valemail-diag-rfc5322domaintoolong-label")
1511  .arg(label, c->locale().toString(ValidatorEmailPrivate::maxDomainLength));
1512  case RFC5322LabelTooLong:
1513  //% "The domain part of the address in the “%1” field contains an element/section "
1514  //% "that exceeds the maximum allowed lenght of %2 characters."
1515  return c->qtTrId("cutelyst-valemail-diag-rfc5322labeltoolong-label")
1516  .arg(label, c->locale().toString(ValidatorEmailPrivate::maxDnsLabelLength));
1517  case RFC5322DomainLiteral:
1518  //% "The domain literal of the address in the “%1” field is not a valid "
1519  //% "RFC 5321 address literal."
1520  return c->qtTrId("cutelyst-valemail-diag-rfc5322domainliteral-label").arg(label);
1521  case RFC5322DomLitOBSDText:
1522  //% "The domain literal of the address in the “%1” field is not a valid "
1523  //% "RFC 5321 domain literal and it contains obsolete characters."
1524  return c->qtTrId("cutelyst-valemail-diag-rfc5322domlitobsdtext-label").arg(label);
1525  case RFC5322IPv6GroupCount:
1526  //% "The IPv6 literal of the address in the “%1” field contains the "
1527  //% "wrong number of groups."
1528  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6groupcount-label").arg(label);
1529  case RFC5322IPv62x2xColon:
1530  //% "The IPv6 literal of the address in the “%1” field contains too "
1531  //% "many :: sequences."
1532  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv62x2xcolon-label").arg(label);
1533  case RFC5322IPv6BadChar:
1534  //% "The IPv6 address of the email address in the “%1” field contains "
1535  //% "an illegal group of characters."
1536  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6badchar-label").arg(label);
1537  case RFC5322IPv6MaxGroups:
1538  //% "The IPv6 address of the email address in the “%1” field has too many groups."
1539  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6maxgroups-label").arg(label);
1540  case RFC5322IPv6ColonStart:
1541  //% "The IPv6 address of the email address in the “%1” field starts "
1542  //% "with a single colon."
1543  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonstart-label").arg(label);
1544  case RFC5322IPv6ColonEnd:
1545  //% "The IPv6 address of the email address in the “%1” field ends with "
1546  //% "a single colon."
1547  return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonend-label").arg(label);
1548  case ErrorExpectingDText:
1549  //% "A domain literal of the address in the “%1” field contains a "
1550  //% "character that is not allowed."
1551  return c->qtTrId("cutelyst-valemail-diag-errexpectingdtext-label").arg(label);
1552  case ErrorNoLocalPart:
1553  //% "The address in the “%1” field has no local part."
1554  return c->qtTrId("cutelyst-valemail-diag-errnolocalpart-label").arg(label);
1555  case ErrorNoDomain:
1556  //% "The address in the “%1” field has no domain part."
1557  return c->qtTrId("cutelyst-valemail-diag-errnodomain-label").arg(label);
1558  case ErrorConsecutiveDots:
1559  //% "The address in the “%1” field must not contain consecutive dots."
1560  return c->qtTrId("cutelyst-valemail-diag-errconsecutivedots-label").arg(label);
1561  case ErrorATextAfterCFWS:
1562  //% "The address in the “%1” field contains text after a comment or "
1563  //% "folding white space."
1564  return c->qtTrId("cutelyst-valemail-diag-erratextaftercfws-label").arg(label);
1565  case ErrorATextAfterQS:
1566  //% "The address in the “%1” field contains text after a quoted string."
1567  return c->qtTrId("cutelyst-valemail-diag-erratextafterqs-label").arg(label);
1568  case ErrorATextAfterDomLit:
1569  //% "Extra characters were found after the end of the domain literal of "
1570  //% "the address in the “%1” field."
1571  return c->qtTrId("cutelyst-valemail-diag-erratextafterdomlit-label").arg(label);
1572  case ErrorExpectingQpair:
1573  //% "The address in the “%1” field contains a character that is not "
1574  //% "allowed in a quoted pair."
1575  return c->qtTrId("cutelyst-valemail-diag-errexpectingqpair-label").arg(label);
1576  case ErrorExpectingAText:
1577  //% "The address in the “%1” field contains a character that is not allowed."
1578  return c->qtTrId("cutelyst-valemail-diag-errexpectingatext-label").arg(label);
1579  case ErrorExpectingQText:
1580  //% "A quoted string in the address in the “%1” field contains a "
1581  //% "character that is not allowed."
1582  return c->qtTrId("cutelyst-valemail-diag-errexpectingqtext-label").arg(label);
1583  case ErrorExpectingCText:
1584  //% "A comment in the address in the “%1” field contains a character "
1585  //% "that is not allowed."
1586  return c->qtTrId("cutelyst-valemail-diag-errexpectingctext-label").arg(label);
1587  case ErrorBackslashEnd:
1588  //% "The address in the “%1” field can't end with a backslash."
1589  return c->qtTrId("cutelyst-valemail-diag-errbackslashend-label").arg(label);
1590  case ErrorDotStart:
1591  //% "Neither part of the address in the “%1” field may begin with a dot."
1592  return c->qtTrId("cutelyst-valemail-diag-errdotstart-label").arg(label);
1593  case ErrorDotEnd:
1594  //% "Neither part of the address in the “%1” field may end with a dot."
1595  return c->qtTrId("cutelyst-valemail-diag-errdotend-label").arg(label);
1597  //% "A domain or subdomain of the address in the “%1” field can not "
1598  //% "begin with a hyphen."
1599  return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenstart-label").arg(label);
1600  case ErrorDomainHyphenEnd:
1601  //% "A domain or subdomain of the address in the “%1” field can not end "
1602  //% "with a hyphen."
1603  return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenend-label").arg(label);
1605  //% "Unclosed quoted string in the address in the “%1” field. (Missing "
1606  //% "double quotation mark)"
1607  return c->qtTrId("cutelyst-valemail-diag-errunclosedquotedstr-label").arg(label);
1608  case ErrorUnclosedComment:
1609  //% "Unclosed comment in the address in the “%1” field. (Missing "
1610  //% "closing parentheses)"
1611  return c->qtTrId("cutelyst-valemail-diag-errunclosedcomment-label").arg(label);
1613  //% "Domain literal of the address in the “%1” field is missing its "
1614  //% "closing bracket."
1615  return c->qtTrId("cutelyst-valemail-diag-erruncloseddomliteral-label").arg(label);
1616  case ErrorFWSCRLFx2:
1617  //% "Folding white space in the address in the “%1” field contains "
1618  //% "consecutive line break sequences (CRLF)."
1619  return c->qtTrId("cutelyst-valemail-diag-errfwscrlfx2-label").arg(label);
1620  case ErrorFWSCRLFEnd:
1621  //% "Folding white space in the address in the “%1” field ends with a "
1622  //% "line break sequence (CRLF)."
1623  return c->qtTrId("cutelyst-valemail-diag-errfwscrlfend-label").arg(label);
1624  case ErrorCRnoLF:
1625  //% "The address in the “%1” field contains a carriage return (CR) that "
1626  //% "is not followed by a line feed (LF)."
1627  return c->qtTrId("cutelyst-valemail-diag-errcrnolf-label").arg(label);
1628  case ErrorFatal:
1629  //% "A fatal error occurred while parsing the address in the “%1” field."
1630  return c->qtTrId("cutelyst-valemail-diag-errfatal-label").arg(label);
1631  default:
1632  return {};
1633  }
1634  }
1635 }
1636 
1638 {
1639  if (label.isEmpty()) {
1640  switch (category) {
1641  case Valid:
1642  //% "Address is valid."
1643  return c->qtTrId("cutelyst-valemail-cat-valid");
1644  case DNSWarn:
1645  //% "Address is valid but a DNS check was not successful."
1646  return c->qtTrId("cutelyst-valemail-cat-dnswarn");
1647  case RFC5321:
1648  //% "Address is valid for SMTP but has unusual elements."
1649  return c->qtTrId("cutelyst-valemail-cat-rfc5321");
1650  case CFWS:
1651  //% "Address is valid within the message but can not be used unmodified "
1652  //% "for the envelope."
1653  return c->qtTrId("cutelyst-valemail-cat-cfws");
1654  case Deprecated:
1655  //% "Address contains deprecated elements but may still be valid in "
1656  //% "restricted contexts."
1657  return c->qtTrId("cutelyst-valemail-cat-deprecated");
1658  case RFC5322:
1659  //% "The address is only valid according to the broad definition of RFC "
1660  //% "5322. It is otherwise invalid."
1661  return c->qtTrId("cutelyst-valemail-cat-rfc5322");
1662  default:
1663  //% "Address is invalid for any purpose."
1664  return c->qtTrId("cutelyst-valemail-cat-invalid");
1665  }
1666  } else {
1667  switch (category) {
1668  case Valid:
1669  //% "The address in the “%1” field is valid."
1670  return c->qtTrId("cutelyst-valemail-cat-valid-label").arg(label);
1671  case DNSWarn:
1672  //% "The address in the “%1” field is valid but a DNS check was not successful."
1673  return c->qtTrId("cutelyst-valemail-cat-dnswarn-label").arg(label);
1674  case RFC5321:
1675  //% "The address in the “%1” field is valid for SMTP but has unusual elements."
1676  return c->qtTrId("cutelyst-valemail-cat-rfc5321-label").arg(label);
1677  case CFWS:
1678  //% "The address in the “%1” field is valid within the message but can "
1679  //% "not be used unmodified for the envelope."
1680  return c->qtTrId("cutelyst-valemail-cat-cfws-label").arg(label);
1681  case Deprecated:
1682  //% "The address in the “%1” field contains deprecated elements but may "
1683  //% "still be valid in restricted contexts."
1684  return c->qtTrId("cutelyst-valemail-cat-deprecated-label").arg(label);
1685  case RFC5322:
1686  //% "The address in the “%1” field is only valid according to the broad "
1687  //% "definition of RFC 5322. It is otherwise invalid."
1688  return c->qtTrId("cutelyst-valemail-cat-rfc5322-label").arg(label);
1689  default:
1690  //% "The address in the “%1” field is invalid for any purpose."
1691  return c->qtTrId("cutelyst-valemail-cat-invalid-label").arg(label);
1692  }
1693  }
1694 }
1695 
1697 {
1698  Category cat = Error;
1699 
1700  const auto diag = static_cast<int>(diagnose);
1701 
1702  if (diag < static_cast<int>(Valid)) {
1703  cat = Valid;
1704  } else if (diag < static_cast<int>(DNSWarn)) {
1705  cat = DNSWarn;
1706  } else if (diag < static_cast<int>(RFC5321)) {
1707  cat = RFC5321;
1708  } else if (diag < static_cast<int>(CFWS)) {
1709  cat = CFWS;
1710  } else if (diag < static_cast<int>(Deprecated)) {
1711  cat = Deprecated;
1712  } else if (diag < static_cast<int>(RFC5322)) {
1713  cat = RFC5322;
1714  }
1715 
1716  return cat;
1717 }
1718 
1720 {
1721  return categoryString(c, category(diagnose), label);
1722 }
1723 
1725  Category threshold,
1726  Options options,
1728 {
1729  ValidatorEmailDiagnoseStruct diag;
1730  bool ret = ValidatorEmailPrivate::checkEmail(email, options, threshold, &diag);
1731 
1732  if (diagnoses) {
1733  *diagnoses = diag.returnStatus;
1734  }
1735 
1736  return ret;
1737 }
1738 
1739 #include "moc_validatoremail.cpp"
The Cutelyst Context.
Definition: context.h:42
QLocale locale() const noexcept
Definition: context.cpp:460
QString qtTrId(const char *id, int n=-1) const
Definition: context.h:656
Checks if the value is a valid email address according to specific RFCs.
static Category category(Diagnose diagnose)
Category
Validation category, used as threshold to define valid addresses.
Diagnose
Single diagnose values that show why an address is not valid.
QString genericValidationError(Context *c, const QVariant &errorData=QVariant()) const override
static QString categoryString(Context *c, Category category, const QString &label={})
static QString diagnoseString(Context *c, Diagnose diagnose, const QString &label={})
ValidatorEmail(const QString &field, Category threshold=RFC5321, Options options=NoOption, const ValidatorMessages &messages=ValidatorMessages(), const QString &defValKey=QString())
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
static bool validate(const QString &email, Category threshold=RFC5321, Options options=NoOption, QList< Diagnose > *diagnoses=nullptr)
Returns true if email is a valid address according to the Category given in the threshold.
The Cutelyst namespace holds all public Cutelyst API.
CarriageReturn
bool isLetterOrNumber(char32_t ucs4)
char16_t & unicode()
void abort()
void finished()
int exec(QEventLoop::ProcessEventsFlags flags)
void quit()
void append(QList::parameter_type value)
bool contains(const AT &value) const const
qsizetype size() const const
QString toString(QDate date, QLocale::FormatType format) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString captured(QStringView name) const const
bool hasMatch() const const
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
void push_back(QChar ch)
QString right(qsizetype n) const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
const QChar * unicode() const const
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.
Definition: validatorrule.h:49