Cutelyst  2.13.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  if (tokenPrior == QChar(QChar::CarriageReturn)) {
960  returnStatus.push_back(ValidatorEmail::ErrorFWSCRLFEnd); // Fatal error
961  break;
962  }
963 
964  if (crlf_count > 0) {
965  crlf_count = 0;
966  }
967 
968  contextPrior = context;
969  context = contextStack.takeLast(); // End of FWS
970 
971  // https://tools.ietf.org/html/rfc5322#section-3.2.2
972  // Runs of FWS, comment, or CFWS that occur between lexical tokens in a
973  // structured header field are semantically interpreted as a single
974  // space character.
975  //
976  // is_email() author's note: This *cannot* mean that we must add a
977  // space to the address wherever CFWS appears. This would result in
978  // any addr-spec that had CFWS outside a quoted string being invalid
979  // for RFC 5321.
980 // if (($context === ISEMAIL_COMPONENT_LOCALPART) || ($context === ISEMAIL_COMPONENT_DOMAIN)) {
981 // $parsedata[$context] .= ISEMAIL_STRING_SP;
982 // $atomlist[$context][$element_count] .= ISEMAIL_STRING_SP;
983 // $element_len++;
984 // }
985 
986  i--; // Look at this token again in the parent context
987  }
988 
989  tokenPrior = token;
990  }
991  break;
992  default:
993  returnStatus.push_back(ValidatorEmail::ErrorFatal);
994  qCCritical(C_VALIDATOR, "ValidatorEmail: Unknown context");
995  break;
996  }
997 
998  if (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) > static_cast<int>(ValidatorEmail::RFC5322)) {
999  break;
1000  }
1001  }
1002 
1003  // Some simple final tests
1004  if (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) < static_cast<int>(ValidatorEmail::RFC5322)) {
1005  if (context == ContextQuotedString) {
1006  returnStatus.push_back(ValidatorEmail::ErrorUnclosedQuotedStr);
1007  } else if (context == ContextQuotedPair) {
1008  returnStatus.push_back(ValidatorEmail::ErrorBackslashEnd);
1009  } else if (context == ContextComment) {
1010  returnStatus.push_back(ValidatorEmail::ErrorUnclosedComment);
1011  } else if (context == ComponentLiteral) {
1012  returnStatus.push_back(ValidatorEmail::ErrorUnclosedDomLiteral);
1013  } else if (token == QChar(QChar::CarriageReturn)) {
1014  returnStatus.push_back(ValidatorEmail::ErrorFWSCRLFEnd);
1015  } else if (parseDomain.isEmpty()) {
1016  returnStatus.push_back(ValidatorEmail::ErrorNoDomain);
1017  } else if (elementLen == 0) {
1018  returnStatus.push_back(ValidatorEmail::ErrorDotEnd);
1019  } else if (hypenFlag) {
1020  returnStatus.push_back(ValidatorEmail::ErrorDomainHyphenEnd);
1021  } else if (parseDomain.size() > 255) {
1022  // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.2
1023  // The maximum total length of a domain name or number is 255 octets.
1024  returnStatus.push_back(ValidatorEmail::RFC5322DomainTooLong);
1025  } else if ((parseLocalPart.size() + 1 + parseDomain.size()) > 254) {
1026  // https://tools.ietf.org/html/rfc5321#section-4.1.2
1027  // Forward-path = Path
1028  //
1029  // Path = "<" [ A-d-l ":" ] Mailbox ">"
1030  //
1031  // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
1032  // The maximum total length of a reverse-path or forward-path is 256
1033  // octets (including the punctuation and element separators).
1034  //
1035  // Thus, even without (obsolete) routing information, the Mailbox can
1036  // only be 254 characters long. This is confirmed by this verified
1037  // erratum to RFC 3696:
1038  //
1039  // https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
1040  // However, there is a restriction in RFC 2821 on the length of an
1041  // address in MAIL and RCPT commands of 254 characters. Since addresses
1042  // that do not fit in those fields are not normally useful, the upper
1043  // limit on address lengths should normally be considered to be 254.
1044  returnStatus.push_back(ValidatorEmail::RFC5322TooLong);
1045  } else if (elementLen > 63) {
1046  returnStatus.push_back(ValidatorEmail::RFC5322LabelTooLong);
1047  }
1048  }
1049 
1050  // Check DNS?
1051  bool dnsChecked = false;
1052 
1053  if (checkDns && (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) < static_cast<int>(threshold))) {
1054  // https://tools.ietf.org/html/rfc5321#section-2.3.5
1055  // Names that can
1056  // be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
1057  // in Section 5) are permitted, as are CNAME RRs whose targets can be
1058  // resolved, in turn, to MX or address RRs.
1059  //
1060  // https://tools.ietf.org/html/rfc5321#section-5.1
1061  // The lookup first attempts to locate an MX record associated with the
1062  // name. If a CNAME record is found, the resulting name is processed as
1063  // if it were the initial name. ... If an empty list of MXs is returned,
1064  // the address is treated as if it was associated with an implicit MX
1065  // RR, with a preference of 0, pointing to that host.
1066 
1067  if (elementCount == 0) {
1068  parseDomain += QLatin1Char('.');
1069  }
1070 
1071  QDnsLookup mxLookup(QDnsLookup::MX, parseDomain);
1072  QEventLoop mxLoop;
1073  QObject::connect(&mxLookup, &QDnsLookup::finished, &mxLoop, &QEventLoop::quit);
1074  QTimer::singleShot(3100, &mxLookup, &QDnsLookup::abort);
1075  mxLookup.lookup();
1076  mxLoop.exec();
1077 
1078  if ((mxLookup.error() == QDnsLookup::NoError) && !mxLookup.mailExchangeRecords().empty()) {
1079  dnsChecked = true;
1080  } else {
1081  returnStatus.push_back(ValidatorEmail::DnsWarnNoMxRecord);
1082  QDnsLookup aLookup(QDnsLookup::A, parseDomain);
1083  QEventLoop aLoop;
1084  QObject::connect(&aLookup, &QDnsLookup::finished, &aLoop, &QEventLoop::quit);
1085  QTimer::singleShot(3100, &aLookup, &QDnsLookup::abort);
1086  aLookup.lookup();
1087  aLoop.exec();
1088 
1089  if ((aLookup.error() == QDnsLookup::NoError) && !aLookup.hostAddressRecords().empty()) {
1090  dnsChecked = true;
1091  } else {
1092  returnStatus.push_back(ValidatorEmail::DnsWarnNoRecord);
1093  }
1094  }
1095  }
1096 
1097  // Check for TLD addresses
1098  // -----------------------
1099  // TLD addresses are specifically allowed in RFC 5321 but they are
1100  // unusual to say the least. We will allocate a separate
1101  // status to these addresses on the basis that they are more likely
1102  // to be typos than genuine addresses (unless we've already
1103  // established that the domain does have an MX record)
1104  //
1105  // https://tools.ietf.org/html/rfc5321#section-2.3.5
1106  // In the case
1107  // of a top-level domain used by itself in an email address, a single
1108  // string is used without any dots. This makes the requirement,
1109  // described in more detail below, that only fully-qualified domain
1110  // names appear in SMTP transactions on the public Internet,
1111  // particularly important where top-level domains are involved.
1112  //
1113  // TLD format
1114  // ----------
1115  // The format of TLDs has changed a number of times. The standards
1116  // used by IANA have been largely ignored by ICANN, leading to
1117  // confusion over the standards being followed. These are not defined
1118  // anywhere, except as a general component of a DNS host name (a label).
1119  // However, this could potentially lead to 123.123.123.123 being a
1120  // valid DNS name (rather than an IP address) and thereby creating
1121  // an ambiguity. The most authoritative statement on TLD formats that
1122  // the author can find is in a (rejected!) erratum to RFC 1123
1123  // submitted by John Klensin, the author of RFC 5321:
1124  //
1125  // https://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
1126  // However, a valid host name can never have the dotted-decimal
1127  // form #.#.#.#, since this change does not permit the highest-level
1128  // component label to start with a digit even if it is not all-numeric.
1129  if (!dnsChecked && (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) < static_cast<int>(ValidatorEmail::DNSWarn))) {
1130  if (elementCount == 0) {
1131  returnStatus.push_back(ValidatorEmail::RFC5321TLD);
1132  }
1133 
1134  if (QStringLiteral("0123456789").contains(atomListDomain[elementCount][0])) {
1135  returnStatus.push_back(ValidatorEmail::RFC5321TLDNumberic);
1136  }
1137  }
1138 
1139  if (returnStatus.size() != 1) {
1140  QList<ValidatorEmail::Diagnose> _rs;
1141  for (int j = 0; j < returnStatus.size(); ++j) {
1142  const ValidatorEmail::Diagnose dia = returnStatus.at(j);
1143  if (!_rs.contains(dia) && (dia != ValidatorEmail::ValidAddress)) {
1144  _rs.append(dia); // clazy:exclude=reserve-candidates
1145  }
1146  }
1147  returnStatus = _rs;
1148 
1149  std::sort(returnStatus.begin(), returnStatus.end(), std::greater<ValidatorEmail::Diagnose>());
1150  }
1151 
1152  const ValidatorEmail::Diagnose finalStatus = returnStatus.at(0);
1153 
1154  if (diagnoseStruct) {
1155  diagnoseStruct->finalStatus = finalStatus;
1156  diagnoseStruct->returnStatus = returnStatus;
1157  diagnoseStruct->localpart = parseLocalPart;
1158  diagnoseStruct->domain = parseDomain;
1159  diagnoseStruct->literal = parseLiteral;
1160  }
1161 
1162  ret = (static_cast<int>(finalStatus) < static_cast<int>(threshold));
1163 
1164  return ret;
1165 }
1166 
1167 QString ValidatorEmail::diagnoseString(Context *c, Diagnose diagnose, const QString &label)
1168 {
1169  QString ret;
1170 
1171  if (label.isEmpty()) {
1172  switch (diagnose) {
1173  case ValidAddress:
1174  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.");
1175  break;
1176  case DnsWarnNoMxRecord:
1177  ret = c->translate("Cutelyst::ValidatorEmail", "Could not find an MX record for this address’ domain but an A record does exist.");
1178  break;
1179  case DnsWarnNoRecord:
1180  ret = c->translate("Cutelyst::ValidatorEmail", "Could neither find an MX record nor an A record for this address’ domain.");
1181  break;
1182  case RFC5321TLD:
1183  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid but at a Top Level Domain.");
1184  break;
1185  case RFC5321TLDNumberic:
1186  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid but the Top Level Domain begins with a number.");
1187  break;
1188  case RFC5321QuotedString:
1189  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid but contains a quoted string.");
1190  break;
1191  case RFC5321AddressLiteral:
1192  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid but uses an IP address instead of a domain name.");
1193  break;
1194  case RFC5321IPv6Deprecated:
1195  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.");
1196  break;
1197  case CFWSComment:
1198  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains comments.");
1199  break;
1200  case CFWSFWS:
1201  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains folding white spaces like line breaks.");
1202  break;
1203  case DeprecatedLocalpart:
1204  ret = c->translate("Cutelyst::ValidatorEmail", "The local part is in a deprecated form.");
1205  break;
1206  case DeprecatedFWS:
1207  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains an obsolete form of folding white spaces.");
1208  break;
1209  case DeprecatedQText:
1210  ret = c->translate("Cutelyst::ValidatorEmail", "A quoted string contains a deprecated character.");
1211  break;
1212  case DeprecatedQP:
1213  ret = c->translate("Cutelyst::ValidatorEmail", "A quoted pair contains a deprecated character.");
1214  break;
1215  case DeprecatedComment:
1216  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains a comment in a position that is deprecated.");
1217  break;
1218  case DeprecatedCText:
1219  ret = c->translate("Cutelyst::ValidatorEmail", "A comment contains a deprecated character.");
1220  break;
1221  case DeprecatedCFWSNearAt:
1222  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains a comment or folding white space around the @ sign.");
1223  break;
1224  case RFC5322Domain:
1225  ret = c->translate("Cutelyst::ValidatorEmail", "Address is RFC 5322 compliant but contains domain characters that are not allowed by DNS.");
1226  break;
1227  case RFC5322TooLong:
1228  ret = c->translate("Cutelyst::ValidatorEmail", "Address is too long.");
1229  break;
1230  case RFC5322LocalTooLong:
1231  ret = c->translate("Cutelyst::ValidatorEmail", "The local part of the address is too long.");
1232  break;
1233  case RFC5322DomainTooLong:
1234  ret = c->translate("Cutelyst::ValidatorEmail", "The domain part is too long.");
1235  break;
1236  case RFC5322LabelTooLong:
1237  ret = c->translate("Cutelyst::ValidatorEmail", "The domain part contains an element that is too long.");
1238  break;
1239  case RFC5322DomainLiteral:
1240  ret = c->translate("Cutelyst::ValidatorEmail", "The domain literal is not a valid RFC 5321 address literal.");
1241  break;
1242  case RFC5322DomLitOBSDText:
1243  ret = c->translate("Cutelyst::ValidatorEmail", "The domain literal is not a valid RFC 5321 domain literal and it contains obsolete characters.");
1244  break;
1245  case RFC5322IPv6GroupCount:
1246  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 literal address contains the wrong number of groups.");
1247  break;
1248  case RFC5322IPv62x2xColon:
1249  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 literal address contains too many :: sequences.");
1250  break;
1251  case RFC5322IPv6BadChar:
1252  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address contains an illegal group of characters.");
1253  break;
1254  case RFC5322IPv6MaxGroups:
1255  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address has too many groups.");
1256  break;
1257  case RFC5322IPv6ColonStart:
1258  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address starts with a single colon.");
1259  break;
1260  case RFC5322IPv6ColonEnd:
1261  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address ends with a single colon.");
1262  break;
1263  case ErrorExpectingDText:
1264  ret = c->translate("Cutelyst::ValidatorEmail", "A domain literal contains a character that is not allowed.");
1265  break;
1266  case ErrorNoLocalPart:
1267  ret = c->translate("Cutelyst::ValidatorEmail", "Address has no local part.");
1268  break;
1269  case ErrorNoDomain:
1270  ret = c->translate("Cutelyst::ValidatorEmail", "Address has no domain part.");
1271  break;
1272  case ErrorConsecutiveDots:
1273  ret = c->translate("Cutelyst::ValidatorEmail", "The address must not contain consecutive dots.");
1274  break;
1275  case ErrorATextAfterCFWS:
1276  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains text after a comment or folding white space.");
1277  break;
1278  case ErrorATextAfterQS:
1279  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains text after a quoted string.");
1280  break;
1281  case ErrorATextAfterDomLit:
1282  ret = c->translate("Cutelyst::ValidatorEmail", "Extra characters were found after the end of the domain literal.");
1283  break;
1284  case ErrorExpectingQpair:
1285  ret = c->translate("Cutelyst::ValidatorEmail", "The Address contains a character that is not allowed in a quoted pair.");
1286  break;
1287  case ErrorExpectingAText:
1288  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains a character that is not allowed.");
1289  break;
1290  case ErrorExpectingQText:
1291  ret = c->translate("Cutelyst::ValidatorEmail", "A quoted string contains a character that is not allowed.");
1292  break;
1293  case ErrorExpectingCText:
1294  ret = c->translate("Cutelyst::ValidatorEmail", "A comment contains a character that is not allowed.");
1295  break;
1296  case ErrorBackslashEnd:
1297  ret = c->translate("Cutelyst::ValidatorEmail", "The address can not end with a backslash.");
1298  break;
1299  case ErrorDotStart:
1300  ret = c->translate("Cutelyst::ValidatorEmail", "Neither part of the address may begin with a dot.");
1301  break;
1302  case ErrorDotEnd:
1303  ret = c->translate("Cutelyst::ValidatorEmail", "Neither part of the address may end with a dot.");
1304  break;
1306  ret = c->translate("Cutelyst::ValidatorEmail", "A domain or subdomain can not begin with a hyphen.");
1307  break;
1308  case ErrorDomainHyphenEnd:
1309  ret = c->translate("Cutelyst::ValidatorEmail", "A domain or subdomain can not end with a hyphen.");
1310  break;
1312  ret = c->translate("Cutelyst::ValidatorEmail", "Unclosed quoted string. (Missing double quotation mark)");
1313  break;
1314  case ErrorUnclosedComment:
1315  ret = c->translate("Cutelyst::ValidatorEmail", "Unclosed comment. (Missing closing parantheses)");
1316  break;
1318  ret = c->translate("Cutelyst::ValidatorEmail", "Domain literal is missing its closing bracket.");
1319  break;
1320  case ErrorFWSCRLFx2:
1321  ret = c->translate("Cutelyst::ValidatorEmail", "Folding white space contains consecutive line break sequences (CRLF).");
1322  break;
1323  case ErrorFWSCRLFEnd:
1324  ret = c->translate("Cutelyst::ValidatorEmail", "Folding white space ends with a line break sequence (CRLF).");
1325  break;
1326  case ErrorCRnoLF:
1327  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains a carriage return (CR) that is not followed by a line feed (LF).");
1328  break;
1329  case ErrorFatal:
1330  ret = c->translate("Cutelyst::ValidatorEmail", "A fatal error occured while parsing the address.");
1331  break;
1332  default:
1333  break;
1334  }
1335 
1336 
1337  } else {
1338 
1339 
1340  switch (diagnose) {
1341  case ValidAddress:
1342  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);
1343  break;
1344  case DnsWarnNoMxRecord:
1345  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);
1346  break;
1347  case DnsWarnNoRecord:
1348  ret = c->translate("Cutelyst::ValidatorEmail", "Could neither find an MX record nor an A record for address’ domain in the “%1” field.").arg(label);
1349  break;
1350  case RFC5321TLD:
1351  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is valid but at a Top Level Domain.").arg(label);
1352  break;
1353  case RFC5321TLDNumberic:
1354  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is valid but the Top Level Domain begins with a number.").arg(label);
1355  break;
1356  case RFC5321QuotedString:
1357  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is valid but contains a quoted string.").arg(label);
1358  break;
1359  case RFC5321AddressLiteral:
1360  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);
1361  break;
1362  case RFC5321IPv6Deprecated:
1363  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);
1364  break;
1365  case CFWSComment:
1366  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains comments.").arg(label);
1367  break;
1368  case CFWSFWS:
1369  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains folding white spaces like line breaks.").arg(label);
1370  break;
1371  case DeprecatedLocalpart:
1372  ret = c->translate("Cutelyst::ValidatorEmail", "The local part of the address in the “%1” field is in a deprecated form.").arg(label);
1373  break;
1374  case DeprecatedFWS:
1375  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains an obsolete form of folding white spaces.").arg(label);
1376  break;
1377  case DeprecatedQText:
1378  ret = c->translate("Cutelyst::ValidatorEmail", "A quoted string in the address in the “%1” field contains a deprecated character.").arg(label);
1379  break;
1380  case DeprecatedQP:
1381  ret = c->translate("Cutelyst::ValidatorEmail", "A quoted pair in the address in the “%1” field contains a deprecate character.").arg(label);
1382  break;
1383  case DeprecatedComment:
1384  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains a comment in a position that is deprecated.").arg(label);
1385  break;
1386  case DeprecatedCText:
1387  ret = c->translate("Cutelyst::ValidatorEmail", "A comment in the address in the “%1” field contains a deprecated character.").arg(label);
1388  break;
1389  case DeprecatedCFWSNearAt:
1390  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains a comment or folding white space around the @ sign.").arg(label);
1391  break;
1392  case RFC5322Domain:
1393  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);
1394  break;
1395  case RFC5322TooLong:
1396  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is too long.").arg(label);
1397  break;
1398  case RFC5322LocalTooLong:
1399  ret = c->translate("Cutelyst::ValidatorEmail", "The local part of the address in the “%1” field is too long.").arg(label);
1400  break;
1401  case RFC5322DomainTooLong:
1402  ret = c->translate("Cutelyst::ValidatorEmail", "The domain part of the address in the “%1” field is too long.").arg(label);
1403  break;
1404  case RFC5322LabelTooLong:
1405  ret = c->translate("Cutelyst::ValidatorEmail", "The domain part of the address in the “%1” field contains an element that is too long.").arg(label);
1406  break;
1407  case RFC5322DomainLiteral:
1408  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);
1409  break;
1410  case RFC5322DomLitOBSDText:
1411  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);
1412  break;
1413  case RFC5322IPv6GroupCount:
1414  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 literal of the address in the “%1” field contains the wrong number of groups.").arg(label);
1415  break;
1416  case RFC5322IPv62x2xColon:
1417  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 literal of the address in the “%1” field contains too many :: sequences.").arg(label);
1418  break;
1419  case RFC5322IPv6BadChar:
1420  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address of the email address in the “%1” field contains an illegal group of characters.").arg(label);
1421  break;
1422  case RFC5322IPv6MaxGroups:
1423  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address of the email address in the “%1” field has too many groups.").arg(label);
1424  break;
1425  case RFC5322IPv6ColonStart:
1426  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address of the email address in the “%1” field starts with a single colon.").arg(label);
1427  break;
1428  case RFC5322IPv6ColonEnd:
1429  ret = c->translate("Cutelyst::ValidatorEmail", "The IPv6 address of the email address in the “%1” field ends with a single colon.").arg(label);
1430  break;
1431  case ErrorExpectingDText:
1432  ret = c->translate("Cutelyst::ValidatorEmail", "A domain literal of the address in the “%1” field contains a character that is not allowed.").arg(label);
1433  break;
1434  case ErrorNoLocalPart:
1435  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field has no local part.").arg(label);
1436  break;
1437  case ErrorNoDomain:
1438  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field has no domain part.").arg(label);
1439  break;
1440  case ErrorConsecutiveDots:
1441  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field must not contain consecutive dots.").arg(label);
1442  break;
1443  case ErrorATextAfterCFWS:
1444  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains text after a comment or folding white space.").arg(label);
1445  break;
1446  case ErrorATextAfterQS:
1447  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains text after a quoted string.").arg(label);
1448  break;
1449  case ErrorATextAfterDomLit:
1450  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);
1451  break;
1452  case ErrorExpectingQpair:
1453  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains a character that is not allowed in a quoted pair.").arg(label);
1454  break;
1455  case ErrorExpectingAText:
1456  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains a character that is not allowed.").arg(label);
1457  break;
1458  case ErrorExpectingQText:
1459  ret = c->translate("Cutelyst::ValidatorEmail", "A quoted string in the address in the “%1” field contains a character that is not allowed.").arg(label);
1460  break;
1461  case ErrorExpectingCText:
1462  ret = c->translate("Cutelyst::ValidatorEmail", "A comment in the address in the “%1” field contains a character that is not allowed.").arg(label);
1463  break;
1464  case ErrorBackslashEnd:
1465  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field can't end with a backslash.").arg(label);
1466  break;
1467  case ErrorDotStart:
1468  ret = c->translate("Cutelyst::ValidatorEmail", "Neither part of the address in the “%1” field may begin with a dot.").arg(label);
1469  break;
1470  case ErrorDotEnd:
1471  ret = c->translate("Cutelyst::ValidatorEmail", "Neither part of the address in the “%1” field may end with a dot.").arg(label);
1472  break;
1474  ret = c->translate("Cutelyst::ValidatorEmail", "A domain or subdomain of the address in the “%1” field can not begin with a hyphen.").arg(label);
1475  break;
1476  case ErrorDomainHyphenEnd:
1477  ret = c->translate("Cutelyst::ValidatorEmail", "A domain or subdomain of the address in the “%1” field can not end with a hyphen.").arg(label);
1478  break;
1480  ret = c->translate("Cutelyst::ValidatorEmail", "Unclosed quoted string in the address in the “%1” field. (Missing double quotation mark)").arg(label);
1481  break;
1482  case ErrorUnclosedComment:
1483  ret = c->translate("Cutelyst::ValidatorEmail", "Unclosed comment in the address in the “%1” field. (Missing closing parantheses)").arg(label);
1484  break;
1486  ret = c->translate("Cutelyst::ValidatorEmail", "Domain literal of the address in the “%1” field is missing its closing bracket.").arg(label);
1487  break;
1488  case ErrorFWSCRLFx2:
1489  ret = c->translate("Cutelyst::ValidatorEmail", "Folding white space in the address in the “%1” field contains consecutive line break sequences (CRLF).").arg(label);
1490  break;
1491  case ErrorFWSCRLFEnd:
1492  ret = c->translate("Cutelyst::ValidatorEmail", "Folding white space in the address in the “%1” field ends with a line break sequence (CRLF).").arg(label);
1493  break;
1494  case ErrorCRnoLF:
1495  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);
1496  break;
1497  case ErrorFatal:
1498  ret = c->translate("Cutelyst::ValidatorEmail", "A fatal error occured while parsing the address in the “%1” field.").arg(label);
1499  break;
1500  default:
1501  break;
1502  }
1503  }
1504 
1505  return ret;
1506 }
1507 
1508 QString ValidatorEmail::categoryString(Context *c, Category category, const QString &label)
1509 {
1510  QString ret;
1511  if (label.isEmpty()) {
1512  switch(category) {
1513  case Valid:
1514  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid.");
1515  break;
1516  case DNSWarn:
1517  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid but a DNS check was not successful.");
1518  break;
1519  case RFC5321:
1520  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid for SMTP but has unusual elements.");
1521  break;
1522  case CFWS:
1523  ret = c->translate("Cutelyst::ValidatorEmail", "Address is valid within the message but can not be used unmodified for the envelope.");
1524  break;
1525  case Deprecated:
1526  ret = c->translate("Cutelyst::ValidatorEmail", "Address contains deprecated elements but my still be valid in restricted contexts.");
1527  break;
1528  case RFC5322:
1529  ret = c->translate("Cutelyst::ValidatorEmail", "The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid.");
1530  break;
1531  default:
1532  ret = c->translate("Cutelyst::ValidatorEmail", "Address is invalid for any purpose.");
1533  break;
1534  }
1535  } else {
1536  switch(category) {
1537  case Valid:
1538  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is valid.").arg(label);
1539  break;
1540  case DNSWarn:
1541  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is valid but a DNS check was not successful.").arg(label);
1542  break;
1543  case RFC5321:
1544  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is valid for SMTP but has unusual elements.").arg(label);
1545  break;
1546  case CFWS:
1547  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);
1548  break;
1549  case Deprecated:
1550  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field contains deprecated elements but my still be valid in restricted contexts.").arg(label);
1551  break;
1552  case RFC5322:
1553  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);
1554  break;
1555  default:
1556  ret = c->translate("Cutelyst::ValidatorEmail", "The address in the “%1” field is invalid for any purpose.").arg(label);
1557  break;
1558  }
1559  }
1560  return ret;
1561 }
1562 
1564 {
1565  Category cat = Error;
1566 
1567  const quint8 diag = static_cast<quint8>(diagnose);
1568 
1569  if (diag < static_cast<quint8>(Valid)) {
1570  cat = Valid;
1571  } else if (diag < static_cast<quint8>(DNSWarn)) {
1572  cat = DNSWarn;
1573  } else if (diag < static_cast<quint8>(RFC5321)) {
1574  cat = RFC5321;
1575  } else if (diag < static_cast<quint8>(CFWS)) {
1576  cat = CFWS;
1577  } else if (diag < static_cast<quint8>(Deprecated)) {
1578  cat = Deprecated;
1579  } else if (diag < static_cast<quint8>(RFC5322)) {
1580  cat = RFC5322;
1581  }
1582 
1583  return cat;
1584 }
1585 
1586 QString ValidatorEmail::categoryString(Context *c, Diagnose diagnose, const QString &label)
1587 {
1588  QString ret;
1589  const Category cat = category(diagnose);
1590  ret = categoryString(c, cat, label);
1591  return ret;
1592 }
1593 
1594 bool ValidatorEmail::validate(const QString &email, Category threshold, Options options, QList<Cutelyst::ValidatorEmail::Diagnose> *diagnoses)
1595 {
1596  bool ret;
1597 
1598  ValidatorEmailDiagnoseStruct diag;
1599  ret = ValidatorEmailPrivate::checkEmail(email, options, threshold, &diag);
1600 
1601  if (diagnoses) {
1602  *diagnoses = diag.returnStatus;
1603  }
1604 
1605  return ret;
1606 }
1607 
1608 #include "moc_validatoremail.cpp"
Cutelyst::ValidatorEmail::ErrorFWSCRLFEnd
@ ErrorFWSCRLFEnd
Definition: validatoremail.h:128
Cutelyst::ValidatorEmail::CFWSFWS
@ CFWSFWS
Definition: validatoremail.h:84
Cutelyst::ParamsMultiMap
QMap< QString, QString > ParamsMultiMap
Definition: paramsmultimap.h:36
Cutelyst::ValidatorEmail::diagnoseString
static QString diagnoseString(Context *c, Diagnose diagnose, const QString &label=QString())
Returns a descriptive and translated string for the diagnose.
Definition: validatoremail.cpp:1167
Cutelyst::ValidatorEmail::CheckDNS
@ CheckDNS
Definition: validatoremail.h:136
Cutelyst::ValidatorEmail::ErrorUnclosedQuotedStr
@ ErrorUnclosedQuotedStr
Definition: validatoremail.h:124
Cutelyst::ValidatorEmail::AllowIDN
@ AllowIDN
Definition: validatoremail.h:138
Cutelyst::ValidatorEmail::RFC5322TooLong
@ RFC5322TooLong
Definition: validatoremail.h:95
Cutelyst::ValidatorEmail::RFC5322
@ RFC5322
Definition: validatoremail.h:62
Cutelyst::ValidatorEmail::RFC5322IPv62x2xColon
@ RFC5322IPv62x2xColon
Definition: validatoremail.h:102
Cutelyst::ValidatorEmail::category
static Category category(Diagnose diagnose)
Returns the category the diagnose belongs to.
Definition: validatoremail.cpp:1563
Cutelyst::ValidatorMessages
Stores custom error messages and the input field label.
Definition: validatorrule.h:144
Cutelyst::ValidatorEmail::RFC5322DomainTooLong
@ RFC5322DomainTooLong
Definition: validatoremail.h:97
Cutelyst::ValidatorRule::value
QString value(const ParamsMultiMap &params) const
Returns the value of the field from the input params.
Definition: validatorrule.cpp:41
Cutelyst::ValidatorEmail::ErrorConsecutiveDots
@ ErrorConsecutiveDots
Definition: validatoremail.h:111
Cutelyst::ValidatorEmail::ErrorATextAfterQS
@ ErrorATextAfterQS
Definition: validatoremail.h:113
Cutelyst::ValidatorRule::label
QString label(Context *c) const
Returns the human readable field label used for generic error messages.
Definition: validatorrule.cpp:58
Cutelyst::ValidatorEmail::ValidatorEmail
ValidatorEmail(const QString &field, Category threshold=RFC5321, Options options=NoOption, const ValidatorMessages &messages=ValidatorMessages(), const QString &defValKey=QString())
Constructs a new email validator.
Definition: validatoremail.cpp:30
Cutelyst::ValidatorEmail::RFC5322IPv6MaxGroups
@ RFC5322IPv6MaxGroups
Definition: validatoremail.h:104
Cutelyst::ValidatorRule::validationError
QString validationError(Context *c, const QVariant &errorData=QVariant()) const
Returns a descriptive error message if validation failed.
Definition: validatorrule.cpp:72
Cutelyst::ValidatorEmail::RFC5322IPv6ColonEnd
@ RFC5322IPv6ColonEnd
Definition: validatoremail.h:106
Cutelyst::ValidatorEmail::RFC5322DomainLiteral
@ RFC5322DomainLiteral
Definition: validatoremail.h:99
Cutelyst::ValidatorEmail::ErrorFatal
@ ErrorFatal
Definition: validatoremail.h:130
Cutelyst::ValidatorEmail::Category
Category
Validation category, used as threshold to define valid addresses.
Definition: validatoremail.h:56
Cutelyst::Context
The Cutelyst Context.
Definition: context.h:50
Cutelyst::ValidatorEmail::DeprecatedCText
@ DeprecatedCText
Definition: validatoremail.h:91
Cutelyst::ValidatorReturnType::extra
QVariant extra
Definition: validatorrule.h:65
Cutelyst::ValidatorEmail::ErrorExpectingQpair
@ ErrorExpectingQpair
Definition: validatoremail.h:115
Cutelyst::ValidatorEmail::genericValidationError
QString genericValidationError(Context *c, const QVariant &errorData=QVariant()) const override
Returns a generic error if validation failed.
Definition: validatoremail.cpp:94
Cutelyst::ValidatorEmail::RFC5322DomLitOBSDText
@ RFC5322DomLitOBSDText
Definition: validatoremail.h:100
Cutelyst::ValidatorEmail::DNSWarn
@ DNSWarn
Definition: validatoremail.h:58
Cutelyst::ValidatorEmail::ErrorDotStart
@ ErrorDotStart
Definition: validatoremail.h:120
Cutelyst::ValidatorEmail::ErrorFWSCRLFx2
@ ErrorFWSCRLFx2
Definition: validatoremail.h:127
Cutelyst::ValidatorEmail::ErrorUnclosedComment
@ ErrorUnclosedComment
Definition: validatoremail.h:125
Cutelyst::ValidatorRule
Base class for all validator rules.
Definition: validatorrule.h:292
Cutelyst::ValidatorEmail::Deprecated
@ Deprecated
Definition: validatoremail.h:61
Cutelyst::ValidatorEmail::RFC5322IPv6ColonStart
@ RFC5322IPv6ColonStart
Definition: validatoremail.h:105
Cutelyst::ValidatorEmail
Checks if the value is a valid email address according to specific RFCs.
Definition: validatoremail.h:49
Cutelyst::ValidatorEmail::DeprecatedComment
@ DeprecatedComment
Definition: validatoremail.h:90
Cutelyst::ValidatorEmail::Diagnose
Diagnose
Single diagnose values that show why an address is not valid.
Definition: validatoremail.h:70
Cutelyst::ValidatorEmail::RFC5321IPv6Deprecated
@ RFC5321IPv6Deprecated
Definition: validatoremail.h:81
Cutelyst::ValidatorEmail::RFC5321AddressLiteral
@ RFC5321AddressLiteral
Definition: validatoremail.h:80
Cutelyst::ValidatorEmail::UTF8Local
@ UTF8Local
Definition: validatoremail.h:137
Cutelyst::ValidatorEmail::CFWSComment
@ CFWSComment
Definition: validatoremail.h:83
Cutelyst::Context::translate
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:473
Cutelyst::ValidatorEmail::RFC5322Domain
@ RFC5322Domain
Definition: validatoremail.h:94
Cutelyst::ValidatorEmail::ErrorNoLocalPart
@ ErrorNoLocalPart
Definition: validatoremail.h:109
Cutelyst::ValidatorEmail::DeprecatedQText
@ DeprecatedQText
Definition: validatoremail.h:88
Cutelyst::ValidatorReturnType
Contains the result of a single input parameter validation.
Definition: validatorrule.h:62
Cutelyst::ValidatorEmail::RFC5321
@ RFC5321
Definition: validatoremail.h:59
Cutelyst::ValidatorEmail::ErrorBackslashEnd
@ ErrorBackslashEnd
Definition: validatoremail.h:119
Cutelyst::ValidatorEmail::Error
@ Error
Definition: validatoremail.h:63
Cutelyst::ValidatorEmail::CFWS
@ CFWS
Definition: validatoremail.h:60
Cutelyst::ValidatorEmail::RFC5321QuotedString
@ RFC5321QuotedString
Definition: validatoremail.h:79
Cutelyst::ValidatorEmail::DnsWarnNoMxRecord
@ DnsWarnNoMxRecord
Definition: validatoremail.h:74
Cutelyst::ValidatorEmail::ErrorUnclosedDomLiteral
@ ErrorUnclosedDomLiteral
Definition: validatoremail.h:126
Cutelyst::ValidatorEmail::ErrorATextAfterCFWS
@ ErrorATextAfterCFWS
Definition: validatoremail.h:112
Cutelyst::ValidatorEmail::ErrorNoDomain
@ ErrorNoDomain
Definition: validatoremail.h:110
Cutelyst::ValidatorEmail::ErrorATextAfterDomLit
@ ErrorATextAfterDomLit
Definition: validatoremail.h:114
Cutelyst
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
Cutelyst::ValidatorEmail::~ValidatorEmail
~ValidatorEmail() override
Deconstructs the email validator.
Definition: validatoremail.cpp:35
Cutelyst::ValidatorEmail::DeprecatedLocalpart
@ DeprecatedLocalpart
Definition: validatoremail.h:86
Cutelyst::ValidatorReturnType::value
QVariant value
Definition: validatorrule.h:64
Cutelyst::ValidatorEmail::DnsWarnNoRecord
@ DnsWarnNoRecord
Definition: validatoremail.h:75
Cutelyst::ValidatorEmail::ErrorExpectingQText
@ ErrorExpectingQText
Definition: validatoremail.h:117
Cutelyst::ValidatorEmail::RFC5321TLD
@ RFC5321TLD
Definition: validatoremail.h:77
Cutelyst::ValidatorEmail::ValidAddress
@ ValidAddress
Definition: validatoremail.h:72
Cutelyst::ValidatorEmail::ErrorCRnoLF
@ ErrorCRnoLF
Definition: validatoremail.h:129
Cutelyst::ValidatorEmail::ErrorExpectingCText
@ ErrorExpectingCText
Definition: validatoremail.h:118
Cutelyst::ValidatorEmail::RFC5322LocalTooLong
@ RFC5322LocalTooLong
Definition: validatoremail.h:96
Cutelyst::ValidatorEmail::RFC5322IPv6BadChar
@ RFC5322IPv6BadChar
Definition: validatoremail.h:103
Cutelyst::ValidatorEmail::categoryString
static QString categoryString(Context *c, Category category, const QString &label=QString())
Returns a descriptive and translated string for the category.
Definition: validatoremail.cpp:1508
Cutelyst::ValidatorEmail::RFC5322IPv6GroupCount
@ RFC5322IPv6GroupCount
Definition: validatoremail.h:101
Cutelyst::ValidatorReturnType::errorMessage
QString errorMessage
Definition: validatorrule.h:63
Cutelyst::ValidatorEmail::RFC5321TLDNumberic
@ RFC5321TLDNumberic
Definition: validatoremail.h:78
Cutelyst::ValidatorEmail::DeprecatedFWS
@ DeprecatedFWS
Definition: validatoremail.h:87
Cutelyst::ValidatorEmail::Valid
@ Valid
Definition: validatoremail.h:57
Cutelyst::ValidatorEmail::ErrorDotEnd
@ ErrorDotEnd
Definition: validatoremail.h:121
Cutelyst::ValidatorEmail::ErrorDomainHyphenStart
@ ErrorDomainHyphenStart
Definition: validatoremail.h:122
Cutelyst::ValidatorEmail::ErrorDomainHyphenEnd
@ ErrorDomainHyphenEnd
Definition: validatoremail.h:123
Cutelyst::ValidatorEmail::DeprecatedQP
@ DeprecatedQP
Definition: validatoremail.h:89
Cutelyst::ValidatorEmail::validate
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.
Cutelyst::ValidatorRule::defaultValue
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 ...
Definition: validatorrule.cpp:162
Cutelyst::ValidatorEmail::RFC5322LabelTooLong
@ RFC5322LabelTooLong
Definition: validatoremail.h:98
Cutelyst::ValidatorEmail::ErrorExpectingAText
@ ErrorExpectingAText
Definition: validatoremail.h:116
Cutelyst::ValidatorEmail::DeprecatedCFWSNearAt
@ DeprecatedCFWSNearAt
Definition: validatoremail.h:92
Cutelyst::ValidatorEmail::ErrorExpectingDText
@ ErrorExpectingDText
Definition: validatoremail.h:108