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
CarriageReturn
bool isLetterOrNumber() const const
ushort unicode() const const
void abort()
void finished()
int exec(ProcessEventsFlags flags)
void quit()
void append(const T &value)
const T & at(int i) const const
iterator begin()
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
bool contains(const T &value) const const
iterator end()
void push_back(const T &value)
int size() const const
T takeLast()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString captured(int nth) const const
bool hasMatch() const const
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
int compare(const QString &other, Qt::CaseSensitivity cs) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString left(int n) const const
int length() const const
QString mid(int position, int n) const const
QString right(int n) const const
int size() const const
QByteArray toAce(const QString &domain)
QVariant fromValue(const T &value)
void setValue(const T &value)
T value() const const
Stores custom error messages and the input field label.
Contains the result of a single input parameter validation.