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