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