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