cutelyst  4.6.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 c, Qt::CaseSensitivity cs) const const
QString toString(QDate date, QLocale::FormatType format) const const
The Cutelyst Context.
Definition: context.h:42
void defaultValue(Context *c, ValidatorReturnType *result) const
void append(QList::parameter_type value)
void abort()
int exec(QEventLoop::ProcessEventsFlags flags)
QByteArray toAce(const QString &domain, QUrl::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
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)