cutelyst 4.3.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
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
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{
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
188 : ValidatorEmail::DeprecatedComment);
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)
202 : ValidatorEmail::ErrorConsecutiveDots);
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)
223 : ValidatorEmail::DeprecatedLocalpart);
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
244 : ValidatorEmail::DeprecatedFWS);
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)
398 : ValidatorEmail::DeprecatedComment);
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)
412 : ValidatorEmail::ErrorConsecutiveDots);
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)
464 : ValidatorEmail::DeprecatedFWS);
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) +
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),
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() > ValidatorEmailPrivate::maxDomainLength) {
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()) >
1101 ValidatorEmailPrivate::maxMailboxLength) {
1102 // https://tools.ietf.org/html/rfc5321#section-4.1.2
1103 // Forward-path = Path
1104 //
1105 // Path = "<" [ A-d-l ":" ] Mailbox ">"
1106 //
1107 // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
1108 // The maximum total length of a reverse-path or forward-path is 256
1109 // octets (including the punctuation and element separators).
1110 //
1111 // Thus, even without (obsolete) routing information, the Mailbox can
1112 // only be 254 characters long. This is confirmed by this verified
1113 // erratum to RFC 3696:
1114 //
1115 // https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
1116 // However, there is a restriction in RFC 2821 on the length of an
1117 // address in MAIL and RCPT commands of 254 characters. Since addresses
1118 // that do not fit in those fields are not normally useful, the upper
1119 // limit on address lengths should normally be considered to be 254.
1120 returnStatus.push_back(ValidatorEmail::RFC5322TooLong);
1121 } else if (elementLen > ValidatorEmailPrivate::maxDnsLabelLength) {
1122 returnStatus.push_back(ValidatorEmail::RFC5322LabelTooLong);
1123 }
1124 }
1125
1126 // Check DNS?
1127 bool dnsChecked = false;
1128
1129 if (checkDns &&
1130 (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) <
1131 static_cast<int>(threshold))) {
1132 // https://tools.ietf.org/html/rfc5321#section-2.3.5
1133 // Names that can
1134 // be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
1135 // in Section 5) are permitted, as are CNAME RRs whose targets can be
1136 // resolved, in turn, to MX or address RRs.
1137 //
1138 // https://tools.ietf.org/html/rfc5321#section-5.1
1139 // The lookup first attempts to locate an MX record associated with the
1140 // name. If a CNAME record is found, the resulting name is processed as
1141 // if it were the initial name. ... If an empty list of MXs is returned,
1142 // the address is treated as if it was associated with an implicit MX
1143 // RR, with a preference of 0, pointing to that host.
1144
1145 if (elementCount == 0) {
1146 parseDomain += QLatin1Char('.');
1147 }
1148
1149 QDnsLookup mxLookup(QDnsLookup::MX, parseDomain);
1150 QEventLoop mxLoop;
1152 QTimer::singleShot(ValidatorEmailPrivate::dnsLookupTimeout, &mxLookup, &QDnsLookup::abort);
1153 mxLookup.lookup();
1154 mxLoop.exec();
1155
1156 if ((mxLookup.error() == QDnsLookup::NoError) && !mxLookup.mailExchangeRecords().empty()) {
1157 dnsChecked = true;
1158 } else {
1159 returnStatus.push_back(ValidatorEmail::DnsWarnNoMxRecord);
1160 QDnsLookup aLookup(QDnsLookup::A, parseDomain);
1161 QEventLoop aLoop;
1164 ValidatorEmailPrivate::dnsLookupTimeout, &aLookup, &QDnsLookup::abort);
1165 aLookup.lookup();
1166 aLoop.exec();
1167
1168 if ((aLookup.error() == QDnsLookup::NoError) && !aLookup.hostAddressRecords().empty()) {
1169 dnsChecked = true;
1170 } else {
1171 returnStatus.push_back(ValidatorEmail::DnsWarnNoRecord);
1172 }
1173 }
1174 }
1175
1176 // Check for TLD addresses
1177 // -----------------------
1178 // TLD addresses are specifically allowed in RFC 5321 but they are
1179 // unusual to say the least. We will allocate a separate
1180 // status to these addresses on the basis that they are more likely
1181 // to be typos than genuine addresses (unless we've already
1182 // established that the domain does have an MX record)
1183 //
1184 // https://tools.ietf.org/html/rfc5321#section-2.3.5
1185 // In the case
1186 // of a top-level domain used by itself in an email address, a single
1187 // string is used without any dots. This makes the requirement,
1188 // described in more detail below, that only fully-qualified domain
1189 // names appear in SMTP transactions on the public Internet,
1190 // particularly important where top-level domains are involved.
1191 //
1192 // TLD format
1193 // ----------
1194 // The format of TLDs has changed a number of times. The standards
1195 // used by IANA have been largely ignored by ICANN, leading to
1196 // confusion over the standards being followed. These are not defined
1197 // anywhere, except as a general component of a DNS host name (a label).
1198 // However, this could potentially lead to 123.123.123.123 being a
1199 // valid DNS name (rather than an IP address) and thereby creating
1200 // an ambiguity. The most authoritative statement on TLD formats that
1201 // the author can find is in a (rejected!) erratum to RFC 1123
1202 // submitted by John Klensin, the author of RFC 5321:
1203 //
1204 // https://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
1205 // However, a valid host name can never have the dotted-decimal
1206 // form #.#.#.#, since this change does not permit the highest-level
1207 // component label to start with a digit even if it is not all-numeric.
1208 if (!dnsChecked &&
1209 (static_cast<int>(*std::max_element(returnStatus.constBegin(), returnStatus.constEnd())) <
1210 static_cast<int>(ValidatorEmail::DNSWarn))) {
1211 if (elementCount == 0) {
1212 returnStatus.push_back(ValidatorEmail::RFC5321TLD);
1213 }
1214
1215 if (QStringLiteral("0123456789").contains(atomListDomain[elementCount][0])) {
1216 returnStatus.push_back(ValidatorEmail::RFC5321TLDNumeric);
1217 }
1218 }
1219
1220 if (returnStatus.size() != 1) {
1222 for (const ValidatorEmail::Diagnose dia : std::as_const(returnStatus)) {
1223 if (!_rs.contains(dia) && (dia != ValidatorEmail::ValidAddress)) {
1224 _rs.append(dia); // clazy:exclude=reserve-candidates
1225 }
1226 }
1227 returnStatus = _rs;
1228
1229 std::sort(returnStatus.begin(), returnStatus.end(), std::greater<>());
1230 }
1231
1232 const ValidatorEmail::Diagnose finalStatus = returnStatus.at(0);
1233
1234 if (diagnoseStruct) {
1235 diagnoseStruct->finalStatus = finalStatus;
1236 diagnoseStruct->returnStatus = returnStatus;
1237 diagnoseStruct->localpart = parseLocalPart;
1238 diagnoseStruct->domain = parseDomain;
1239 diagnoseStruct->literal = parseLiteral;
1240 }
1241
1242 return static_cast<int>(finalStatus) < static_cast<int>(threshold);
1243}
1244
1246{
1247 if (label.isEmpty()) {
1248 switch (diagnose) {
1249 case ValidAddress:
1250 //% "Address is valid. Please note that this does not mean that both the "
1251 //% "address and the domain actually exist. This address could be issued "
1252 //% "by the domain owner without breaking the rules of any RFCs."
1253 return c->qtTrId("cutelyst-valemail-diag-valid");
1254 case DnsWarnNoMxRecord:
1255 //% "Could not find an MX record for this address’ domain but an A record exists."
1256 return c->qtTrId("cutelyst-valemail-diag-nomx");
1257 case DnsWarnNoRecord:
1258 //% "Could neither find an MX record nor an A record for this address’ domain."
1259 return c->qtTrId("cutelyst-valemail-diag-noarec");
1260 case RFC5321TLD:
1261 //% "Address is valid but at a Top Level Domain."
1262 return c->qtTrId("cutelyst-valemail-diag-rfc5321tld");
1263 case RFC5321TLDNumeric:
1264 //% "Address is valid but the Top Level Domain begins with a number."
1265 return c->qtTrId("cutelyst-valemail-diag-rfc5321tldnumeric");
1267 //% "Address is valid but contains a quoted string."
1268 return c->qtTrId("cutelyst-valemail-diag-rfc5321quotedstring");
1270 //% "Address is valid but uses an IP address instead of a domain name."
1271 return c->qtTrId("cutelyst-valemail-diag-rfc5321addressliteral");
1273 //% "Address is valid but uses an IP address that contains a :: only "
1274 //% "eliding one zero group. All implementations must accept and be "
1275 //% "able to handle any legitimate RFC 4291 format."
1276 return c->qtTrId("cutelyst-valemail-diag-rfc5321ipv6deprecated");
1277 case CFWSComment:
1278 //% "Address contains comments."
1279 return c->qtTrId("cutelyst-valemail-diag-cfwscomment");
1280 case CFWSFWS:
1281 //% "Address contains folding white spaces like line breaks."
1282 return c->qtTrId("cutelyst-valemail-diag-cfwsfws");
1284 //% "The local part is in a deprecated form."
1285 return c->qtTrId("cutelyst-valemail-diag-deprecatedlocalpart");
1286 case DeprecatedFWS:
1287 //% "Address contains an obsolete form of folding white spaces."
1288 return c->qtTrId("cutelyst-valemail-diag-deprecatedfws");
1289 case DeprecatedQText:
1290 //% "A quoted string contains a deprecated character."
1291 return c->qtTrId("cutelyst-valemail-diag-deprecatedqtext");
1292 case DeprecatedQP:
1293 //% "A quoted pair contains a deprecated character."
1294 return c->qtTrId("cutelyst-valemail-diag-deprecatedqp");
1295 case DeprecatedComment:
1296 //% "Address contains a comment in a position that is deprecated."
1297 return c->qtTrId("cutelyst-valemail-diag-deprecatedcomment");
1298 case DeprecatedCText:
1299 //% "A comment contains a deprecated character."
1300 return c->qtTrId("cutelyst-valemail-diag-deprecatedctext");
1302 //% "Address contains a comment or folding white space around the @ sign."
1303 return c->qtTrId("cutelyst-valemail-diag-cfwsnearat");
1304 case RFC5322Domain:
1305 //% "Address is RFC 5322 compliant but contains domain characters that "
1306 //% "are not allowed by DNS."
1307 return c->qtTrId("cutelyst-valemail-diag-rfc5322domain");
1308 case RFC5322TooLong:
1309 //% "The address exceeds the maximum allowed length of %1 characters."
1310 return c->qtTrId("cutelyst-valemail-diag-rfc5322toolong")
1311 .arg(c->locale().toString(ValidatorEmailPrivate::maxMailboxLength));
1313 //% "The local part of the address exceeds the maximum allowed length "
1314 //% "of %1 characters."
1315 return c->qtTrId("cutelyst-valemail-diag-rfc5322localtoolong")
1316 .arg(c->locale().toString(ValidatorEmailPrivate::maxLocalPartLength));
1318 //% "The domain part exceeds the maximum allowed length of %1 characters."
1319 return c->qtTrId("cutelyst-valemail-diag-rfc5322domaintoolong")
1320 .arg(c->locale().toString(ValidatorEmailPrivate::maxDomainLength));
1322 //% "One of the labels/sections in the domain part exceeds the maximum allowed "
1323 //% "length of %1 characters."
1324 return c->qtTrId("cutelyst-valemail-diag-rfc5322labeltoolong")
1325 .arg(c->locale().toString(ValidatorEmailPrivate::maxDnsLabelLength));
1327 //% "The domain literal is not a valid RFC 5321 address literal."
1328 return c->qtTrId("cutelyst-valemail-diag-rfc5322domainliteral");
1330 //% "The domain literal is not a valid RFC 5321 domain literal and it "
1331 //% "contains obsolete characters."
1332 return c->qtTrId("cutelyst-valemail-diag-rfc5322domlitobsdtext");
1334 //% "The IPv6 literal address contains the wrong number of groups."
1335 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6groupcount");
1337 //% "The IPv6 literal address contains too many :: sequences."
1338 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv62x2xcolon");
1339 case RFC5322IPv6BadChar:
1340 //% "The IPv6 address contains an illegal group of characters."
1341 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6badchar");
1343 //% "The IPv6 address has too many groups."
1344 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6maxgroups");
1346 //% "The IPv6 address starts with a single colon."
1347 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonstart");
1349 //% "The IPv6 address ends with a single colon."
1350 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonend");
1352 //% "A domain literal contains a character that is not allowed."
1353 return c->qtTrId("cutelyst-valemail-diag-errexpectingdtext");
1354 case ErrorNoLocalPart:
1355 //% "Address has no local part."
1356 return c->qtTrId("cutelyst-valemail-diag-errnolocalpart");
1357 case ErrorNoDomain:
1358 //% "Address has no domain part."
1359 return c->qtTrId("cutelyst-valemail-diag-errnodomain");
1361 //% "The address must not contain consecutive dots."
1362 return c->qtTrId("cutelyst-valemail-diag-errconsecutivedots");
1364 //% "Address contains text after a comment or folding white space."
1365 return c->qtTrId("cutelyst-valemail-diag-erratextaftercfws");
1366 case ErrorATextAfterQS:
1367 //% "Address contains text after a quoted string."
1368 return c->qtTrId("cutelyst-valemail-diag-erratextafterqs");
1370 //% "Extra characters were found after the end of the domain literal."
1371 return c->qtTrId("cutelyst-valemail-diag-erratextafterdomlit");
1373 //% "The Address contains a character that is not allowed in a quoted pair."
1374 return c->qtTrId("cutelyst-valemail-diag-errexpectingqpair");
1376 //% "Address contains a character that is not allowed."
1377 return c->qtTrId("cutelyst-valemail-diag-errexpectingatext");
1379 //% "A quoted string contains a character that is not allowed."
1380 return c->qtTrId("cutelyst-valemail-diag-errexpectingqtext");
1382 //% "A comment contains a character that is not allowed."
1383 return c->qtTrId("cutelyst-valemail-diag-errexpectingctext");
1384 case ErrorBackslashEnd:
1385 //% "The address can not end with a backslash."
1386 return c->qtTrId("cutelyst-valemail-diag-errbackslashend");
1387 case ErrorDotStart:
1388 //% "Neither part of the address may begin with a dot."
1389 return c->qtTrId("cutelyst-valemail-diag-errdotstart");
1390 case ErrorDotEnd:
1391 //% "Neither part of the address may end with a dot."
1392 return c->qtTrId("cutelyst-valemail-diag-errdotend");
1394 //% "A domain or subdomain can not begin with a hyphen."
1395 return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenstart");
1397 //% "A domain or subdomain can not end with a hyphen."
1398 return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenend");
1400 //% "Unclosed quoted string. (Missing double quotation mark)"
1401 return c->qtTrId("cutelyst-valemail-diag-errunclosedquotedstr");
1403 //% "Unclosed comment. (Missing closing parentheses)"
1404 return c->qtTrId("cutelyst-valemail-diag-errunclosedcomment");
1406 //% "Domain literal is missing its closing bracket."
1407 return c->qtTrId("cutelyst-valemail-diag-erruncloseddomliteral");
1408 case ErrorFWSCRLFx2:
1409 //% "Folding white space contains consecutive line break sequences (CRLF)."
1410 return c->qtTrId("cutelyst-valemail-diag-errfwscrlfx2");
1411 case ErrorFWSCRLFEnd:
1412 //% "Folding white space ends with a line break sequence (CRLF)."
1413 return c->qtTrId("cutelyst-valemail-diag-errfwscrlfend");
1414 case ErrorCRnoLF:
1415 //% "Address contains a carriage return (CR) that is not followed by a "
1416 //% "line feed (LF)."
1417 return c->qtTrId("cutelyst-valemail-diag-errcrnolf");
1418 case ErrorFatal:
1419 //% "A fatal error occurred while parsing the address."
1420 return c->qtTrId("cutelyst-valemail-diag-errfatal");
1421 default:
1422 return {};
1423 }
1424
1425 } else {
1426
1427 switch (diagnose) {
1428 case ValidAddress:
1429 //% "The address in the “%1” field is valid. Please note that this does not mean "
1430 //% "that both the address and the domain actually exist. This address could be "
1431 //% "issued by the domain owner without breaking the rules of any RFCs."
1432 return c->qtTrId("cutelyst-valemail-diag-valid-label").arg(label);
1433 case DnsWarnNoMxRecord:
1434 //% "Could not find an MX record for the address’ domain in the “%1” "
1435 //% "field but an A record exists."
1436 return c->qtTrId("cutelyst-valemail-diag-nomx-label").arg(label);
1437 case DnsWarnNoRecord:
1438 //% "Could neither find an MX record nor an A record for the address’ "
1439 //% "domain in the “%1” field."
1440 return c->qtTrId("cutelyst-valemail-diag-noarec-label").arg(label);
1441 case RFC5321TLD:
1442 //% "The address in the “%1” field is valid but at a Top Level Domain."
1443 return c->qtTrId("cutelyst-valemail-diag-rfc5321tld-label").arg(label);
1444 case RFC5321TLDNumeric:
1445 //% "The address in the “%1” field is valid but the Top Level Domain "
1446 //% "begins with a number."
1447 return c->qtTrId("cutelyst-valemail-diag-rfc5321tldnumeric-label").arg(label);
1449 //% "The address in the “%1” field is valid but contains a quoted string."
1450 return c->qtTrId("cutelyst-valemail-diag-rfc5321quotedstring-label").arg(label);
1452 //% "The address in the “%1” field is valid but uses an IP address "
1453 //% "instead of a domain name."
1454 return c->qtTrId("cutelyst-valemail-diag-rfc5321addressliteral-label").arg(label);
1456 //% "The address in the “%1” field is valid but uses an IP address that "
1457 //% "contains a :: only eliding one zero group. All implementations "
1458 //% "must accept and be able to handle any legitimate RFC 4291 format."
1459 return c->qtTrId("cutelyst-valemail-diag-rfc5321ipv6deprecated-label").arg(label);
1460 case CFWSComment:
1461 //% "The address in the “%1” field contains comments."
1462 return c->qtTrId("cutelyst-valemail-diag-cfwscomment-label").arg(label);
1463 case CFWSFWS:
1464 //% "The address in the “%1” field contains folding white spaces like "
1465 //% "line breaks."
1466 return c->qtTrId("cutelyst-valemail-diag-cfwsfws-label").arg(label);
1468 //% "The local part of the address in the “%1” field is in a deprecated form."
1469 return c->qtTrId("cutelyst-valemail-diag-deprecatedlocalpart-label").arg(label);
1470 case DeprecatedFWS:
1471 //% "The address in the “%1” field contains an obsolete form of folding "
1472 //% "white spaces."
1473 return c->qtTrId("cutelyst-valemail-diag-deprecatedfws-label").arg(label);
1474 case DeprecatedQText:
1475 //% "A quoted string in the address in the “%1” field contains a "
1476 //% "deprecated character."
1477 return c->qtTrId("cutelyst-valemail-diag-deprecatedqtext-label").arg(label);
1478 case DeprecatedQP:
1479 //% "A quoted pair in the address in the “%1” field contains a "
1480 //% "deprecate character."
1481 return c->qtTrId("cutelyst-valemail-diag-deprecatedqp-label").arg(label);
1482 case DeprecatedComment:
1483 //% "The address in the “%1” field contains a comment in a position "
1484 //% "that is deprecated."
1485 return c->qtTrId("cutelyst-valemail-diag-deprecatedcomment-label").arg(label);
1486 case DeprecatedCText:
1487 //% "A comment in the address in the “%1” field contains a deprecated character."
1488 return c->qtTrId("cutelyst-valemail-diag-deprecatedctext-label").arg(label);
1490 //% "The address in the “%1” field contains a comment or folding white "
1491 //% "space around the @ sign."
1492 return c->qtTrId("cutelyst-valemail-diag-cfwsnearat-label").arg(label);
1493 case RFC5322Domain:
1494 //% "The address in the “%1” field is RFC 5322 compliant but contains "
1495 //% "domain characters that are not allowed by DNS."
1496 return c->qtTrId("cutelyst-valemail-diag-rfc5322domain-label").arg(label);
1497 case RFC5322TooLong:
1498 //% "The address in the “%1” field exceeds the maximum allowed length "
1499 //% "of %2 characters."
1500 return c->qtTrId("cutelyst-valemail-diag-rfc5322toolong-label")
1501 .arg(label, c->locale().toString(ValidatorEmailPrivate::maxMailboxLength));
1503 //% "The local part of the address in the “%1” field exceeds the maximum allowed "
1504 //% "length of %2 characters."
1505 return c->qtTrId("cutelyst-valemail-diag-rfc5322localtoolong-label")
1506 .arg(label, c->locale().toString(ValidatorEmailPrivate::maxLocalPartLength));
1508 //% "The domain part of the address in the “%1” field exceeds the maximum "
1509 //% "allowed length of %2 characters."
1510 return c->qtTrId("cutelyst-valemail-diag-rfc5322domaintoolong-label")
1511 .arg(label, c->locale().toString(ValidatorEmailPrivate::maxDomainLength));
1513 //% "The domain part of the address in the “%1” field contains an element/section "
1514 //% "that exceeds the maximum allowed lenght of %2 characters."
1515 return c->qtTrId("cutelyst-valemail-diag-rfc5322labeltoolong-label")
1516 .arg(label, c->locale().toString(ValidatorEmailPrivate::maxDnsLabelLength));
1518 //% "The domain literal of the address in the “%1” field is not a valid "
1519 //% "RFC 5321 address literal."
1520 return c->qtTrId("cutelyst-valemail-diag-rfc5322domainliteral-label").arg(label);
1522 //% "The domain literal of the address in the “%1” field is not a valid "
1523 //% "RFC 5321 domain literal and it contains obsolete characters."
1524 return c->qtTrId("cutelyst-valemail-diag-rfc5322domlitobsdtext-label").arg(label);
1526 //% "The IPv6 literal of the address in the “%1” field contains the "
1527 //% "wrong number of groups."
1528 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6groupcount-label").arg(label);
1530 //% "The IPv6 literal of the address in the “%1” field contains too "
1531 //% "many :: sequences."
1532 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv62x2xcolon-label").arg(label);
1533 case RFC5322IPv6BadChar:
1534 //% "The IPv6 address of the email address in the “%1” field contains "
1535 //% "an illegal group of characters."
1536 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6badchar-label").arg(label);
1538 //% "The IPv6 address of the email address in the “%1” field has too many groups."
1539 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6maxgroups-label").arg(label);
1541 //% "The IPv6 address of the email address in the “%1” field starts "
1542 //% "with a single colon."
1543 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonstart-label").arg(label);
1545 //% "The IPv6 address of the email address in the “%1” field ends with "
1546 //% "a single colon."
1547 return c->qtTrId("cutelyst-valemail-diag-rfc5322ipv6colonend-label").arg(label);
1549 //% "A domain literal of the address in the “%1” field contains a "
1550 //% "character that is not allowed."
1551 return c->qtTrId("cutelyst-valemail-diag-errexpectingdtext-label").arg(label);
1552 case ErrorNoLocalPart:
1553 //% "The address in the “%1” field has no local part."
1554 return c->qtTrId("cutelyst-valemail-diag-errnolocalpart-label").arg(label);
1555 case ErrorNoDomain:
1556 //% "The address in the “%1” field has no domain part."
1557 return c->qtTrId("cutelyst-valemail-diag-errnodomain-label").arg(label);
1559 //% "The address in the “%1” field must not contain consecutive dots."
1560 return c->qtTrId("cutelyst-valemail-diag-errconsecutivedots-label").arg(label);
1562 //% "The address in the “%1” field contains text after a comment or "
1563 //% "folding white space."
1564 return c->qtTrId("cutelyst-valemail-diag-erratextaftercfws-label").arg(label);
1565 case ErrorATextAfterQS:
1566 //% "The address in the “%1” field contains text after a quoted string."
1567 return c->qtTrId("cutelyst-valemail-diag-erratextafterqs-label").arg(label);
1569 //% "Extra characters were found after the end of the domain literal of "
1570 //% "the address in the “%1” field."
1571 return c->qtTrId("cutelyst-valemail-diag-erratextafterdomlit-label").arg(label);
1573 //% "The address in the “%1” field contains a character that is not "
1574 //% "allowed in a quoted pair."
1575 return c->qtTrId("cutelyst-valemail-diag-errexpectingqpair-label").arg(label);
1577 //% "The address in the “%1” field contains a character that is not allowed."
1578 return c->qtTrId("cutelyst-valemail-diag-errexpectingatext-label").arg(label);
1580 //% "A quoted string in the address in the “%1” field contains a "
1581 //% "character that is not allowed."
1582 return c->qtTrId("cutelyst-valemail-diag-errexpectingqtext-label").arg(label);
1584 //% "A comment in the address in the “%1” field contains a character "
1585 //% "that is not allowed."
1586 return c->qtTrId("cutelyst-valemail-diag-errexpectingctext-label").arg(label);
1587 case ErrorBackslashEnd:
1588 //% "The address in the “%1” field can't end with a backslash."
1589 return c->qtTrId("cutelyst-valemail-diag-errbackslashend-label").arg(label);
1590 case ErrorDotStart:
1591 //% "Neither part of the address in the “%1” field may begin with a dot."
1592 return c->qtTrId("cutelyst-valemail-diag-errdotstart-label").arg(label);
1593 case ErrorDotEnd:
1594 //% "Neither part of the address in the “%1” field may end with a dot."
1595 return c->qtTrId("cutelyst-valemail-diag-errdotend-label").arg(label);
1597 //% "A domain or subdomain of the address in the “%1” field can not "
1598 //% "begin with a hyphen."
1599 return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenstart-label").arg(label);
1601 //% "A domain or subdomain of the address in the “%1” field can not end "
1602 //% "with a hyphen."
1603 return c->qtTrId("cutelyst-valemail-diag-errdomainhyphenend-label").arg(label);
1605 //% "Unclosed quoted string in the address in the “%1” field. (Missing "
1606 //% "double quotation mark)"
1607 return c->qtTrId("cutelyst-valemail-diag-errunclosedquotedstr-label").arg(label);
1609 //% "Unclosed comment in the address in the “%1” field. (Missing "
1610 //% "closing parentheses)"
1611 return c->qtTrId("cutelyst-valemail-diag-errunclosedcomment-label").arg(label);
1613 //% "Domain literal of the address in the “%1” field is missing its "
1614 //% "closing bracket."
1615 return c->qtTrId("cutelyst-valemail-diag-erruncloseddomliteral-label").arg(label);
1616 case ErrorFWSCRLFx2:
1617 //% "Folding white space in the address in the “%1” field contains "
1618 //% "consecutive line break sequences (CRLF)."
1619 return c->qtTrId("cutelyst-valemail-diag-errfwscrlfx2-label").arg(label);
1620 case ErrorFWSCRLFEnd:
1621 //% "Folding white space in the address in the “%1” field ends with a "
1622 //% "line break sequence (CRLF)."
1623 return c->qtTrId("cutelyst-valemail-diag-errfwscrlfend-label").arg(label);
1624 case ErrorCRnoLF:
1625 //% "The address in the “%1” field contains a carriage return (CR) that "
1626 //% "is not followed by a line feed (LF)."
1627 return c->qtTrId("cutelyst-valemail-diag-errcrnolf-label").arg(label);
1628 case ErrorFatal:
1629 //% "A fatal error occurred while parsing the address in the “%1” field."
1630 return c->qtTrId("cutelyst-valemail-diag-errfatal-label").arg(label);
1631 default:
1632 return {};
1633 }
1634 }
1635}
1636
1638{
1639 if (label.isEmpty()) {
1640 switch (category) {
1641 case Valid:
1642 //% "Address is valid."
1643 return c->qtTrId("cutelyst-valemail-cat-valid");
1644 case DNSWarn:
1645 //% "Address is valid but a DNS check was not successful."
1646 return c->qtTrId("cutelyst-valemail-cat-dnswarn");
1647 case RFC5321:
1648 //% "Address is valid for SMTP but has unusual elements."
1649 return c->qtTrId("cutelyst-valemail-cat-rfc5321");
1650 case CFWS:
1651 //% "Address is valid within the message but can not be used unmodified "
1652 //% "for the envelope."
1653 return c->qtTrId("cutelyst-valemail-cat-cfws");
1654 case Deprecated:
1655 //% "Address contains deprecated elements but may still be valid in "
1656 //% "restricted contexts."
1657 return c->qtTrId("cutelyst-valemail-cat-deprecated");
1658 case RFC5322:
1659 //% "The address is only valid according to the broad definition of RFC "
1660 //% "5322. It is otherwise invalid."
1661 return c->qtTrId("cutelyst-valemail-cat-rfc5322");
1662 default:
1663 //% "Address is invalid for any purpose."
1664 return c->qtTrId("cutelyst-valemail-cat-invalid");
1665 }
1666 } else {
1667 switch (category) {
1668 case Valid:
1669 //% "The address in the “%1” field is valid."
1670 return c->qtTrId("cutelyst-valemail-cat-valid-label").arg(label);
1671 case DNSWarn:
1672 //% "The address in the “%1” field is valid but a DNS check was not successful."
1673 return c->qtTrId("cutelyst-valemail-cat-dnswarn-label").arg(label);
1674 case RFC5321:
1675 //% "The address in the “%1” field is valid for SMTP but has unusual elements."
1676 return c->qtTrId("cutelyst-valemail-cat-rfc5321-label").arg(label);
1677 case CFWS:
1678 //% "The address in the “%1” field is valid within the message but can "
1679 //% "not be used unmodified for the envelope."
1680 return c->qtTrId("cutelyst-valemail-cat-cfws-label").arg(label);
1681 case Deprecated:
1682 //% "The address in the “%1” field contains deprecated elements but may "
1683 //% "still be valid in restricted contexts."
1684 return c->qtTrId("cutelyst-valemail-cat-deprecated-label").arg(label);
1685 case RFC5322:
1686 //% "The address in the “%1” field is only valid according to the broad "
1687 //% "definition of RFC 5322. It is otherwise invalid."
1688 return c->qtTrId("cutelyst-valemail-cat-rfc5322-label").arg(label);
1689 default:
1690 //% "The address in the “%1” field is invalid for any purpose."
1691 return c->qtTrId("cutelyst-valemail-cat-invalid-label").arg(label);
1692 }
1693 }
1694}
1695
1697{
1698 Category cat = Error;
1699
1700 const auto diag = static_cast<int>(diagnose);
1701
1702 if (diag < static_cast<int>(Valid)) {
1703 cat = Valid;
1704 } else if (diag < static_cast<int>(DNSWarn)) {
1705 cat = DNSWarn;
1706 } else if (diag < static_cast<int>(RFC5321)) {
1707 cat = RFC5321;
1708 } else if (diag < static_cast<int>(CFWS)) {
1709 cat = CFWS;
1710 } else if (diag < static_cast<int>(Deprecated)) {
1711 cat = Deprecated;
1712 } else if (diag < static_cast<int>(RFC5322)) {
1713 cat = RFC5322;
1714 }
1715
1716 return cat;
1717}
1718
1720{
1721 return categoryString(c, category(diagnose), label);
1722}
1723
1725 Category threshold,
1726 Options options,
1728{
1729 ValidatorEmailDiagnoseStruct diag;
1730 bool ret = ValidatorEmailPrivate::checkEmail(email, options, threshold, &diag);
1731
1732 if (diagnoses) {
1733 *diagnoses = diag.returnStatus;
1734 }
1735
1736 return ret;
1737}
1738
1739#include "moc_validatoremail.cpp"
The Cutelyst Context.
Definition context.h:42
QLocale locale() const noexcept
Definition context.cpp:460
QString qtTrId(const char *id, int n=-1) const
Definition context.h:656
Checks if the value is a valid email address according to specific RFCs.
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())
Base class for all validator rules.
QString validationError(Context *c, const QVariant &errorData={}) const
QString label(Context *c) const
void defaultValue(Context *c, ValidatorReturnType *result) const
QString value(const ParamsMultiMap &params) const
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.
CarriageReturn
bool isLetterOrNumber(char32_t ucs4)
char16_t & unicode()
void abort()
void finished()
int exec(QEventLoop::ProcessEventsFlags flags)
void quit()
void append(QList::parameter_type value)
bool contains(const AT &value) const const
qsizetype size() const const
QString toString(QDate date, QLocale::FormatType format) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString captured(QStringView name) const const
bool hasMatch() const const
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
void push_back(QChar ch)
QString right(qsizetype n) const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
const QChar * unicode() const const
QByteArray toAce(const QString &domain, QUrl::AceProcessingOptions options)
void setValue(QVariant &&value)
T value() const const
Stores custom error messages and the input field label.
Contains the result of a single input parameter validation.