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