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"
QString captured(int nth) const const
int indexOf(QChar ch, int 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.
Stores custom error messages and the input field label.
const T & at(int i) const const
int size() 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
int size() const const
void finished()
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
The Cutelyst Context.
Definition: context.h:38
void append(const T &value)
void abort()
int exec(QEventLoop::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)
Checks if the value is a valid email address according to specific RFCs.
bool isEmpty() const const
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) 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.
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(int n) const const
ushort unicode() const const
void push_back(QChar ch)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
void setValue(const T &value)
bool contains(const T &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 mid(int position, int n) const const
QString genericValidationError(Context *c, const QVariant &errorData=QVariant()) const override
Returns a generic error if validation failed.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
int length() const const
QString left(int n) const const
QString fromLatin1(const char *str, int size)
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.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int compare(const QString &other, Qt::CaseSensitivity cs) const 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.
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 ...
bool isLetterOrNumber() const const