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