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