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