KCodecs

kemailaddress.cpp
1 /*
2  SPDX-FileCopyrightText: 2004 Matt Douhan <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "kemailaddress.h"
8 #include "kcodecs.h"
9 #include "kcodecs_debug.h"
10 
11 #include <QByteArray>
12 #include <QRegularExpression>
13 #include <QUrl>
14 
15 using namespace KEmailAddress;
16 
17 //-----------------------------------------------------------------------------
19 {
20  // Features:
21  // - always ignores quoted characters
22  // - ignores everything (including parentheses and commas)
23  // inside quoted strings
24  // - supports nested comments
25  // - ignores everything (including double quotes and commas)
26  // inside comments
27 
28  QStringList list;
29 
30  if (aStr.isEmpty()) {
31  return list;
32  }
33 
34  QString addr;
35  uint addrstart = 0;
36  int commentlevel = 0;
37  bool insidequote = false;
38 
39  for (int index = 0; index < aStr.length(); index++) {
40  // the following conversion to latin1 is o.k. because
41  // we can safely ignore all non-latin1 characters
42  switch (aStr[index].toLatin1()) {
43  case '"' : // start or end of quoted string
44  if (commentlevel == 0) {
45  insidequote = !insidequote;
46  }
47  break;
48  case '(' : // start of comment
49  if (!insidequote) {
50  ++commentlevel;
51  }
52  break;
53  case ')' : // end of comment
54  if (!insidequote) {
55  if (commentlevel > 0) {
56  --commentlevel;
57  } else {
58  return list;
59  }
60  }
61  break;
62  case '\\' : // quoted character
63  index++; // ignore the quoted character
64  break;
65  case ',' :
66  case ';' :
67  if (!insidequote && (commentlevel == 0)) {
68  addr = aStr.mid(addrstart, index - addrstart);
69  if (!addr.isEmpty()) {
70  list += addr.simplified();
71  }
72  addrstart = index + 1;
73  }
74  break;
75  }
76  }
77  // append the last address to the list
78  if (!insidequote && (commentlevel == 0)) {
79  addr = aStr.mid(addrstart, aStr.length() - addrstart);
80  if (!addr.isEmpty()) {
81  list += addr.simplified();
82  }
83  }
84 
85  return list;
86 }
87 
88 //-----------------------------------------------------------------------------
89 // Used by KEmailAddress::splitAddress(...) and KEmailAddress::firstEmailAddress(...).
90 KEmailAddress::EmailParseResult splitAddressInternal(const QByteArray &address,
91  QByteArray &displayName,
92  QByteArray &addrSpec,
93  QByteArray &comment,
94  bool allowMultipleAddresses)
95 {
96  // qCDebug(KCODECS_LOG) << "address";
97  displayName = "";
98  addrSpec = "";
99  comment = "";
100 
101  if (address.isEmpty()) {
102  return AddressEmpty;
103  }
104 
105  // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
106  // The purpose is to extract a displayable string from the mailboxes.
107  // Comments in the addr-spec are not handled. No error checking is done.
108 
109  enum {
110  TopLevel,
111  InComment,
112  InAngleAddress
113  } context = TopLevel;
114  bool inQuotedString = false;
115  int commentLevel = 0;
116  bool stop = false;
117 
118  for (const char *p = address.data(); *p && !stop; ++p) {
119  switch (context) {
120  case TopLevel : {
121  switch (*p) {
122  case '"' :
123  inQuotedString = !inQuotedString;
124  displayName += *p;
125  break;
126  case '(' :
127  if (!inQuotedString) {
128  context = InComment;
129  commentLevel = 1;
130  } else {
131  displayName += *p;
132  }
133  break;
134  case '<' :
135  if (!inQuotedString) {
136  context = InAngleAddress;
137  } else {
138  displayName += *p;
139  }
140  break;
141  case '\\' : // quoted character
142  displayName += *p;
143  ++p; // skip the '\'
144  if (*p) {
145  displayName += *p;
146  } else {
147  return UnexpectedEnd;
148  }
149  break;
150  case ',' :
151  if (!inQuotedString) {
152  if (allowMultipleAddresses) {
153  stop = true;
154  } else {
155  return UnexpectedComma;
156  }
157  } else {
158  displayName += *p;
159  }
160  break;
161  default :
162  displayName += *p;
163  }
164  break;
165  }
166  case InComment : {
167  switch (*p) {
168  case '(' :
169  ++commentLevel;
170  comment += *p;
171  break;
172  case ')' :
173  --commentLevel;
174  if (commentLevel == 0) {
175  context = TopLevel;
176  comment += ' '; // separate the text of several comments
177  } else {
178  comment += *p;
179  }
180  break;
181  case '\\' : // quoted character
182  comment += *p;
183  ++p; // skip the '\'
184  if (*p) {
185  comment += *p;
186  } else {
187  return UnexpectedEnd;
188  }
189  break;
190  default :
191  comment += *p;
192  }
193  break;
194  }
195  case InAngleAddress : {
196  switch (*p) {
197  case '"' :
198  inQuotedString = !inQuotedString;
199  addrSpec += *p;
200  break;
201  case '>' :
202  if (!inQuotedString) {
203  context = TopLevel;
204  } else {
205  addrSpec += *p;
206  }
207  break;
208  case '\\' : // quoted character
209  addrSpec += *p;
210  ++p; // skip the '\'
211  if (*p) {
212  addrSpec += *p;
213  } else {
214  return UnexpectedEnd;
215  }
216  break;
217  default :
218  addrSpec += *p;
219  }
220  break;
221  }
222  } // switch ( context )
223  }
224  // check for errors
225  if (inQuotedString) {
226  return UnbalancedQuote;
227  }
228  if (context == InComment) {
229  return UnbalancedParens;
230  }
231  if (context == InAngleAddress) {
232  return UnclosedAngleAddr;
233  }
234 
235  displayName = displayName.trimmed();
236  comment = comment.trimmed();
237  addrSpec = addrSpec.trimmed();
238 
239  if (addrSpec.isEmpty()) {
240  if (displayName.isEmpty()) {
241  return NoAddressSpec;
242  } else {
243  addrSpec = displayName;
244  displayName.truncate(0);
245  }
246  }
247  /*
248  qCDebug(KCODECS_LOG) << "display-name : \"" << displayName << "\"";
249  qCDebug(KCODECS_LOG) << "comment : \"" << comment << "\"";
250  qCDebug(KCODECS_LOG) << "addr-spec : \"" << addrSpec << "\"";
251  */
252  return AddressOk;
253 }
254 
255 //-----------------------------------------------------------------------------
257  QByteArray &displayName,
258  QByteArray &addrSpec,
259  QByteArray &comment)
260 {
261  return splitAddressInternal(address, displayName, addrSpec, comment,
262  false/* don't allow multiple addresses */);
263 }
264 
265 //-----------------------------------------------------------------------------
267  QString &displayName,
268  QString &addrSpec,
269  QString &comment)
270 {
271  QByteArray d, a, c;
272  // FIXME: toUtf8() is probably not safe here, what if the second byte of a multi-byte character
273  // has the same code as one of the ASCII characters that splitAddress uses as delimiters?
274  EmailParseResult result = splitAddress(address.toUtf8(), d, a, c);
275 
276  if (result == AddressOk) {
277  displayName = QString::fromUtf8(d);
278  addrSpec = QString::fromUtf8(a);
279  comment = QString::fromUtf8(c);
280  }
281  return result;
282 }
283 
284 //-----------------------------------------------------------------------------
286 {
287  // If we are passed an empty string bail right away no need to process
288  // further and waste resources
289  if (aStr.isEmpty()) {
290  return AddressEmpty;
291  }
292 
293  // count how many @'s are in the string that is passed to us
294  // if 0 or > 1 take action
295  // at this point to many @'s cannot bail out right away since
296  // @ is allowed in qoutes, so we use a bool to keep track
297  // and then make a judgment further down in the parser
298 
299  bool tooManyAtsFlag = false;
300 
301  int atCount = aStr.count(QLatin1Char('@'));
302  if (atCount > 1) {
303  tooManyAtsFlag = true;
304  } else if (atCount == 0) {
305  return TooFewAts;
306  }
307 
308  int dotCount = aStr.count(QLatin1Char('.'));
309 
310  // The main parser, try and catch all weird and wonderful
311  // mistakes users and/or machines can create
312 
313  enum {
314  TopLevel,
315  InComment,
316  InAngleAddress
317  } context = TopLevel;
318  bool inQuotedString = false;
319  int commentLevel = 0;
320 
321  unsigned int strlen = aStr.length();
322 
323  for (unsigned int index = 0; index < strlen; index++) {
324  switch (context) {
325  case TopLevel : {
326  switch (aStr[index].toLatin1()) {
327  case '"' :
328  inQuotedString = !inQuotedString;
329  break;
330  case '(' :
331  if (!inQuotedString) {
332  context = InComment;
333  commentLevel = 1;
334  }
335  break;
336  case '[' :
337  if (!inQuotedString) {
338  return InvalidDisplayName;
339  }
340  break;
341  case ']' :
342  if (!inQuotedString) {
343  return InvalidDisplayName;
344  }
345  break;
346  case ':' :
347  if (!inQuotedString) {
348  return DisallowedChar;
349  }
350  break;
351  case '<' :
352  if (!inQuotedString) {
353  context = InAngleAddress;
354  }
355  break;
356  case '\\' : // quoted character
357  ++index; // skip the '\'
358  if ((index + 1) > strlen) {
359  return UnexpectedEnd;
360  }
361  break;
362  case ',' :
363  if (!inQuotedString) {
364  return UnexpectedComma;
365  }
366  break;
367  case ')' :
368  if (!inQuotedString) {
369  return UnbalancedParens;
370  }
371  break;
372  case '>' :
373  if (!inQuotedString) {
374  return UnopenedAngleAddr;
375  }
376  break;
377  case '@' :
378  if (!inQuotedString) {
379  if (index == 0) { // Missing local part
380  return MissingLocalPart;
381  } else if (index == strlen - 1) {
382  return MissingDomainPart;
383  }
384  } else if (inQuotedString) {
385  --atCount;
386  if (atCount == 1) {
387  tooManyAtsFlag = false;
388  }
389  }
390  break;
391  case '.' :
392  if (inQuotedString) {
393  --dotCount;
394  }
395  break;
396  }
397  break;
398  }
399  case InComment : {
400  switch (aStr[index].toLatin1()) {
401  case '(' :
402  ++commentLevel;
403  break;
404  case ')' :
405  --commentLevel;
406  if (commentLevel == 0) {
407  context = TopLevel;
408  }
409  break;
410  case '\\' : // quoted character
411  ++index; // skip the '\'
412  if ((index + 1) > strlen) {
413  return UnexpectedEnd;
414  }
415  break;
416  }
417  break;
418  }
419 
420  case InAngleAddress : {
421  switch (aStr[index].toLatin1()) {
422  case ',' :
423  if (!inQuotedString) {
424  return UnexpectedComma;
425  }
426  break;
427  case '"' :
428  inQuotedString = !inQuotedString;
429  break;
430  case '@' :
431  if (inQuotedString) {
432  --atCount;
433  }
434  if (atCount == 1) {
435  tooManyAtsFlag = false;
436  }
437  break;
438  case '.' :
439  if (inQuotedString) {
440  --dotCount;
441  }
442  break;
443  case '>' :
444  if (!inQuotedString) {
445  context = TopLevel;
446  break;
447  }
448  break;
449  case '\\' : // quoted character
450  ++index; // skip the '\'
451  if ((index + 1) > strlen) {
452  return UnexpectedEnd;
453  }
454  break;
455  }
456  break;
457  }
458  }
459  }
460 
461  if (dotCount == 0 && !inQuotedString) {
462  return TooFewDots;
463  }
464 
465  if (atCount == 0 && !inQuotedString) {
466  return TooFewAts;
467  }
468 
469  if (inQuotedString) {
470  return UnbalancedQuote;
471  }
472 
473  if (context == InComment) {
474  return UnbalancedParens;
475  }
476 
477  if (context == InAngleAddress) {
478  return UnclosedAngleAddr;
479  }
480 
481  if (tooManyAtsFlag) {
482  return TooManyAts;
483  }
484 
485  return AddressOk;
486 }
487 
488 //-----------------------------------------------------------------------------
490  QString &badAddr)
491 {
492  if (aStr.isEmpty()) {
493  return AddressEmpty;
494  }
495 
496  const QStringList list = splitAddressList(aStr);
497 
499  EmailParseResult errorCode = AddressOk;
500  for (it = list.begin(); it != list.end(); ++it) {
501  qCDebug(KCODECS_LOG) << " *it" << (*it);
502  errorCode = isValidAddress(*it);
503  if (errorCode != AddressOk) {
504  badAddr = (*it);
505  break;
506  }
507  }
508  return errorCode;
509 }
510 
511 //-----------------------------------------------------------------------------
513 {
514  switch (errorCode) {
515  case TooManyAts :
516  return QObject::tr("The email address you entered is not valid because it "
517  "contains more than one @. "
518  "You will not create valid messages if you do not "
519  "change your address.");
520  case TooFewAts :
521  return QObject::tr("The email address you entered is not valid because it "
522  "does not contain a @. "
523  "You will not create valid messages if you do not "
524  "change your address.");
525  case AddressEmpty :
526  return QObject::tr("You have to enter something in the email address field.");
527  case MissingLocalPart :
528  return QObject::tr("The email address you entered is not valid because it "
529  "does not contain a local part.");
530  case MissingDomainPart :
531  return QObject::tr("The email address you entered is not valid because it "
532  "does not contain a domain part.");
533  case UnbalancedParens :
534  return QObject::tr("The email address you entered is not valid because it "
535  "contains unclosed comments/brackets.");
536  case AddressOk :
537  return QObject::tr("The email address you entered is valid.");
538  case UnclosedAngleAddr :
539  return QObject::tr("The email address you entered is not valid because it "
540  "contains an unclosed angle bracket.");
541  case UnopenedAngleAddr :
542  return QObject::tr("The email address you entered is not valid because it "
543  "contains too many closing angle brackets.");
544  case UnexpectedComma :
545  return QObject::tr("The email address you have entered is not valid because it "
546  "contains an unexpected comma.");
547  case UnexpectedEnd :
548  return QObject::tr("The email address you entered is not valid because it ended "
549  "unexpectedly. This probably means you have used an escaping "
550  "type character like a '\\' as the last character in your "
551  "email address.");
552  case UnbalancedQuote :
553  return QObject::tr("The email address you entered is not valid because it "
554  "contains quoted text which does not end.");
555  case NoAddressSpec :
556  return QObject::tr("The email address you entered is not valid because it "
557  "does not seem to contain an actual email address, i.e. "
558  "something of the form [email protected]");
559  case DisallowedChar :
560  return QObject::tr("The email address you entered is not valid because it "
561  "contains an illegal character.");
562  case InvalidDisplayName :
563  return QObject::tr("The email address you have entered is not valid because it "
564  "contains an invalid display name.");
565  case TooFewDots :
566  return QObject::tr("The email address you entered is not valid because it "
567  "does not contain a \'.\'. "
568  "You will not create valid messages if you do not "
569  "change your address.");
570 
571  }
572  return QObject::tr("Unknown problem with email address");
573 }
574 
575 //-----------------------------------------------------------------------------
577 {
578  // If we are passed an empty string bail right away no need to process further
579  // and waste resources
580  if (aStr.isEmpty()) {
581  return false;
582  }
583 
584  int atChar = aStr.lastIndexOf(QLatin1Char('@'));
585  QString domainPart = aStr.mid(atChar + 1);
586  QString localPart = aStr.left(atChar);
587 
588  // Both of these parts must be non empty
589  // after all we cannot have emails like:
590  // @kde.org, or [email protected]
591  if (localPart.isEmpty() || domainPart.isEmpty()) {
592  return false;
593  }
594 
595  bool inQuotedString = false;
596  int atCount = localPart.count(QLatin1Char('@'));
597 
598  unsigned int strlen = localPart.length();
599  for (unsigned int index = 0; index < strlen; index++) {
600  switch (localPart[ index ].toLatin1()) {
601  case '"' :
602  inQuotedString = !inQuotedString;
603  break;
604  case '@' :
605  if (inQuotedString) {
606  --atCount;
607  }
608  break;
609  }
610  }
611 
612  QString addrRx;
613 
614  if (localPart[ 0 ] == QLatin1Char('\"') || localPart[ localPart.length() - 1 ] == QLatin1Char('\"')) {
615  addrRx = QStringLiteral("\"[[email protected]]*[\\[email protected]]*[[email protected]]\"@");
616  } else {
617  addrRx = QStringLiteral("[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@");
618  }
619  if (domainPart[ 0 ] == QLatin1Char('[') || domainPart[ domainPart.length() - 1 ] == QLatin1Char(']')) {
620  addrRx += QStringLiteral("\\[[0-9]{1,3}(\\.[0-9]{1,3}){3}\\]");
621  } else {
622  addrRx += QStringLiteral("[\\w#-]+(\\.[\\w#-]+)*");
623  }
624 
626  QRegularExpression::UseUnicodePropertiesOption);
627  return rx.match(aStr).hasMatch();
628 }
629 
630 //-----------------------------------------------------------------------------
632 {
633  return QObject::tr("The email address you entered is not valid because it "
634  "does not seem to contain an actual email address, i.e. "
635  "something of the form [email protected]");
636 }
637 
638 //-----------------------------------------------------------------------------
640 {
642  return extractEmailAddress(address, errorMessage);
643 }
644 
646 {
647  QByteArray dummy1, dummy2, addrSpec;
648  const EmailParseResult result =
649  splitAddressInternal(address, dummy1, addrSpec, dummy2,
650  false/* don't allow multiple addresses */);
651  if (result != AddressOk) {
652  addrSpec = QByteArray();
653  if (result != AddressEmpty) {
654  errorMessage = emailParseResultToString(result);
655  qCDebug(KCODECS_LOG) << "Input:" << address << "\nError:"
656  << errorMessage;
657  }
658  } else {
659  errorMessage.clear();
660  }
661 
662  return addrSpec;
663 }
664 
665 //-----------------------------------------------------------------------------
667 {
669  return extractEmailAddress(address, errorMessage);
670 }
671 
673 {
674  return QString::fromUtf8(extractEmailAddress(address.toUtf8(), errorMessage));
675 }
676 
677 //-----------------------------------------------------------------------------
679 {
681  return firstEmailAddress(addresses, errorMessage);
682 }
683 
685 {
686  QByteArray dummy1, dummy2, addrSpec;
687  const EmailParseResult result =
688  splitAddressInternal(addresses, dummy1, addrSpec, dummy2,
689  true/* allow multiple addresses */);
690  if (result != AddressOk) {
691  addrSpec = QByteArray();
692  if (result != AddressEmpty) {
693 
694  errorMessage = emailParseResultToString(result);
695  qCDebug(KCODECS_LOG) << "Input: aStr\nError:"
696  << errorMessage;
697  }
698  } else {
699  errorMessage.clear();
700  }
701 
702  return addrSpec;
703 }
704 
705 //-----------------------------------------------------------------------------
707 {
709  return firstEmailAddress(addresses, errorMessage);
710 }
711 
712 QString KEmailAddress::firstEmailAddress(const QString &addresses, QString &errorMessage)
713 {
714  return QString::fromUtf8(firstEmailAddress(addresses.toUtf8(), errorMessage));
715 }
716 
717 //-----------------------------------------------------------------------------
719  QString &mail, QString &name)
720 {
721  name.clear();
722  mail.clear();
723 
724  const int len = aStr.length();
725  const char cQuotes = '"';
726 
727  bool bInComment = false;
728  bool bInQuotesOutsideOfEmail = false;
729  int i = 0, iAd = 0, iMailStart = 0, iMailEnd = 0;
730  QChar c;
731  unsigned int commentstack = 0;
732 
733  // Find the '@' of the email address
734  // skipping all '@' inside "(...)" comments:
735  while (i < len) {
736  c = aStr[i];
737  if (QLatin1Char('(') == c) {
738  ++commentstack;
739  }
740  if (QLatin1Char(')') == c) {
741  --commentstack;
742  }
743  bInComment = commentstack != 0;
744  if (QLatin1Char('"') == c && !bInComment) {
745  bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
746  }
747 
748  if (!bInComment && !bInQuotesOutsideOfEmail) {
749  if (QLatin1Char('@') == c) {
750  iAd = i;
751  break; // found it
752  }
753  }
754  ++i;
755  }
756 
757  if (!iAd) {
758  // We suppose the user is typing the string manually and just
759  // has not finished typing the mail address part.
760  // So we take everything that's left of the '<' as name and the rest as mail
761  for (i = 0; len > i; ++i) {
762  c = aStr[i];
763  if (QLatin1Char('<') != c) {
764  name.append(c);
765  } else {
766  break;
767  }
768  }
769  mail = aStr.mid(i + 1);
770  if (mail.endsWith(QLatin1Char('>'))) {
771  mail.truncate(mail.length() - 1);
772  }
773 
774  } else {
775  // Loop backwards until we find the start of the string
776  // or a ',' that is outside of a comment
777  // and outside of quoted text before the leading '<'.
778  bInComment = false;
779  bInQuotesOutsideOfEmail = false;
780  for (i = iAd - 1; 0 <= i; --i) {
781  c = aStr[i];
782  if (bInComment) {
783  if (QLatin1Char('(') == c) {
784  if (!name.isEmpty()) {
785  name.prepend(QLatin1Char(' '));
786  }
787  bInComment = false;
788  } else {
789  name.prepend(c); // all comment stuff is part of the name
790  }
791  } else if (bInQuotesOutsideOfEmail) {
792  if (QLatin1Char(cQuotes) == c) {
793  bInQuotesOutsideOfEmail = false;
794  } else if (c != QLatin1Char('\\')) {
795  name.prepend(c);
796  }
797  } else {
798  // found the start of this addressee ?
799  if (QLatin1Char(',') == c) {
800  break;
801  }
802  // stuff is before the leading '<' ?
803  if (iMailStart) {
804  if (QLatin1Char(cQuotes) == c) {
805  bInQuotesOutsideOfEmail = true; // end of quoted text found
806  } else {
807  name.prepend(c);
808  }
809  } else {
810  switch (c.toLatin1()) {
811  case '<':
812  iMailStart = i;
813  break;
814  case ')':
815  if (!name.isEmpty()) {
816  name.prepend(QLatin1Char(' '));
817  }
818  bInComment = true;
819  break;
820  default:
821  if (QLatin1Char(' ') != c) {
822  mail.prepend(c);
823  }
824  }
825  }
826  }
827  }
828 
829  name = name.simplified();
830  mail = mail.simplified();
831 
832  if (mail.isEmpty()) {
833  return false;
834  }
835 
836  mail.append(QLatin1Char('@'));
837 
838  // Loop forward until we find the end of the string
839  // or a ',' that is outside of a comment
840  // and outside of quoted text behind the trailing '>'.
841  bInComment = false;
842  bInQuotesOutsideOfEmail = false;
843  int parenthesesNesting = 0;
844  for (i = iAd + 1; len > i; ++i) {
845  c = aStr[i];
846  if (bInComment) {
847  if (QLatin1Char(')') == c) {
848  if (--parenthesesNesting == 0) {
849  bInComment = false;
850  if (!name.isEmpty()) {
851  name.append(QLatin1Char(' '));
852  }
853  } else {
854  // nested ")", add it
855  name.append(QLatin1Char(')')); // name can't be empty here
856  }
857  } else {
858  if (QLatin1Char('(') == c) {
859  // nested "("
860  ++parenthesesNesting;
861  }
862  name.append(c); // all comment stuff is part of the name
863  }
864  } else if (bInQuotesOutsideOfEmail) {
865  if (QLatin1Char(cQuotes) == c) {
866  bInQuotesOutsideOfEmail = false;
867  } else if (c != QLatin1Char('\\')) {
868  name.append(c);
869  }
870  } else {
871  // found the end of this addressee ?
872  if (QLatin1Char(',') == c) {
873  break;
874  }
875  // stuff is behind the trailing '>' ?
876  if (iMailEnd) {
877  if (QLatin1Char(cQuotes) == c) {
878  bInQuotesOutsideOfEmail = true; // start of quoted text found
879  } else {
880  name.append(c);
881  }
882  } else {
883  switch (c.toLatin1()) {
884  case '>':
885  iMailEnd = i;
886  break;
887  case '(':
888  if (!name.isEmpty()) {
889  name.append(QLatin1Char(' '));
890  }
891  if (++parenthesesNesting > 0) {
892  bInComment = true;
893  }
894  break;
895  default:
896  if (QLatin1Char(' ') != c) {
897  mail.append(c);
898  }
899  }
900  }
901  }
902  }
903  }
904 
905  name = name.simplified();
906  mail = mail.simplified();
907 
908  return !(name.isEmpty() || mail.isEmpty());
909 }
910 
911 //-----------------------------------------------------------------------------
912 bool KEmailAddress::compareEmail(const QString &email1, const QString &email2,
913  bool matchName)
914 {
915  QString e1Name, e1Email, e2Name, e2Email;
916 
917  extractEmailAddressAndName(email1, e1Email, e1Name);
918  extractEmailAddressAndName(email2, e2Email, e2Name);
919 
920  return e1Email == e2Email &&
921  (!matchName || (e1Name == e2Name));
922 }
923 
924 //-----------------------------------------------------------------------------
925 // Used internally by normalizedAddress()
926 QString removeBidiControlChars(const QString &input)
927 {
928  const int LRO = 0x202D;
929  const int RLO = 0x202E;
930  const int LRE = 0x202A;
931  const int RLE = 0x202B;
932  QString result = input;
933  result.remove(LRO);
934  result.remove(RLO);
935  result.remove(LRE);
936  result.remove(RLE);
937  return result;
938 }
939 
941  const QString &addrSpec,
942  const QString &comment)
943 {
944  const QString realDisplayName = removeBidiControlChars(displayName);
945  if (realDisplayName.isEmpty() && comment.isEmpty()) {
946  return addrSpec;
947  } else if (comment.isEmpty()) {
948  if (!realDisplayName.startsWith(QLatin1Char('\"'))) {
949  return quoteNameIfNecessary(realDisplayName) + QLatin1String(" <") + addrSpec + QLatin1Char('>');
950  } else {
951  return realDisplayName + QLatin1String(" <") + addrSpec + QLatin1Char('>');
952  }
953  } else if (realDisplayName.isEmpty()) {
954  return quoteNameIfNecessary(comment) + QLatin1String(" <") + addrSpec + QLatin1Char('>');
955  } else {
956  return realDisplayName + QLatin1String(" (") + comment + QLatin1String(") <") + addrSpec + QLatin1Char('>');
957  }
958 }
959 
960 //-----------------------------------------------------------------------------
962 {
963  const int atPos = addrSpec.lastIndexOf(QLatin1Char('@'));
964  if (atPos == -1) {
965  return addrSpec;
966  }
967 
968  QString idn = QUrl::fromAce(addrSpec.mid(atPos + 1).toLatin1());
969  if (idn.isEmpty()) {
970  return QString();
971  }
972 
973  return addrSpec.left(atPos + 1) + idn;
974 }
975 
976 //-----------------------------------------------------------------------------
978 {
979  const int atPos = addrSpec.lastIndexOf(QLatin1Char('@'));
980  if (atPos == -1) {
981  return addrSpec;
982  }
983 
984  QString idn = QLatin1String(QUrl::toAce(addrSpec.mid(atPos + 1)));
985  if (idn.isEmpty()) {
986  return addrSpec;
987  }
988 
989  return addrSpec.left(atPos + 1) + idn;
990 }
991 
992 //-----------------------------------------------------------------------------
994 {
995  // qCDebug(KCODECS_LOG) << str;
996  if (str.isEmpty()) {
997  return str;
998  }
999 
1000  const QStringList addressList = splitAddressList(str);
1001  QStringList normalizedAddressList;
1002 
1003  QByteArray displayName, addrSpec, comment;
1004 
1005  for (QStringList::ConstIterator it = addressList.begin();
1006  (it != addressList.end());
1007  ++it) {
1008  if (!(*it).isEmpty()) {
1009  if (splitAddress((*it).toUtf8(),
1010  displayName, addrSpec, comment) == AddressOk) {
1011  QByteArray cs;
1012  displayName = KCodecs::decodeRFC2047String(displayName, &cs).toUtf8();
1013  comment = KCodecs::decodeRFC2047String(comment, &cs).toUtf8();
1014 
1015  normalizedAddressList
1016  << normalizedAddress(QString::fromUtf8(displayName),
1017  fromIdn(QString::fromUtf8(addrSpec)),
1018  QString::fromUtf8(comment));
1019  }
1020  }
1021  }
1022  /*
1023  qCDebug(KCODECS_LOG) << "normalizedAddressList: \""
1024  << normalizedAddressList.join( ", " )
1025  << "\"";
1026  */
1027  return normalizedAddressList.join(QStringLiteral(", "));
1028 }
1029 
1030 //-----------------------------------------------------------------------------
1032 {
1033  //qCDebug(KCODECS_LOG) << str;
1034  if (str.isEmpty()) {
1035  return str;
1036  }
1037 
1038  const QStringList addressList = splitAddressList(str);
1039  QStringList normalizedAddressList;
1040 
1041  QByteArray displayName, addrSpec, comment;
1042 
1043  for (QStringList::ConstIterator it = addressList.begin();
1044  (it != addressList.end());
1045  ++it) {
1046  if (!(*it).isEmpty()) {
1047  if (splitAddress((*it).toUtf8(),
1048  displayName, addrSpec, comment) == AddressOk) {
1049 
1050  normalizedAddressList << normalizedAddress(QString::fromUtf8(displayName),
1051  toIdn(QString::fromUtf8(addrSpec)),
1052  QString::fromUtf8(comment));
1053  }
1054  }
1055  }
1056 
1057  /*
1058  qCDebug(KCODECS_LOG) << "normalizedAddressList: \""
1059  << normalizedAddressList.join( ", " )
1060  << "\"";
1061  */
1062  return normalizedAddressList.join(QStringLiteral(", "));
1063 }
1064 
1065 //-----------------------------------------------------------------------------
1066 // Escapes unescaped doublequotes in str.
1067 static QString escapeQuotes(const QString &str)
1068 {
1069  if (str.isEmpty()) {
1070  return QString();
1071  }
1072 
1073  QString escaped;
1074  // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
1075  escaped.reserve(2 * str.length());
1076  unsigned int len = 0;
1077  for (int i = 0; i < str.length(); ++i, ++len) {
1078  if (str[i] == QLatin1Char('"')) { // unescaped doublequote
1079  escaped[len] = QLatin1Char('\\');
1080  ++len;
1081  } else if (str[i] == QLatin1Char('\\')) { // escaped character
1082  escaped[len] = QLatin1Char('\\');
1083  ++len;
1084  ++i;
1085  if (i >= str.length()) { // handle trailing '\' gracefully
1086  break;
1087  }
1088  }
1089  escaped[len] = str[i];
1090  }
1091  escaped.truncate(len);
1092  return escaped;
1093 }
1094 
1095 //-----------------------------------------------------------------------------
1097 {
1098  QString quoted = str;
1099 
1100  const QRegularExpression needQuotes(QStringLiteral("[^ 0-9A-Za-z\\x{0080}-\\x{FFFF}]"));
1101  // avoid double quoting
1102  if ((quoted[0] == QLatin1Char('"')) && (quoted[quoted.length() - 1] == QLatin1Char('"'))) {
1103  quoted = QLatin1String("\"") + escapeQuotes(quoted.mid(1, quoted.length() - 2)) + QLatin1String("\"");
1104  } else if (quoted.indexOf(needQuotes) != -1) {
1105  quoted = QLatin1String("\"") + escapeQuotes(quoted) + QLatin1String("\"");
1106  }
1107 
1108  return quoted;
1109 }
1110 
1112 {
1113  const QByteArray encodedPath = KCodecs::encodeRFC2047String(mailbox, "utf-8");
1114  QUrl mailtoUrl;
1115  mailtoUrl.setScheme(QStringLiteral("mailto"));
1116  mailtoUrl.setPath(QLatin1String(encodedPath));
1117  return mailtoUrl;
1118 }
1119 
1121 {
1122  Q_ASSERT(mailtoUrl.scheme() == QLatin1String("mailto"));
1123  return KCodecs::decodeRFC2047String(mailtoUrl.path());
1124 }
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
QString & append(QChar ch)
KCODECS_EXPORT QUrl encodeMailtoUrl(const QString &mailbox)
Creates a valid mailto: URL from the given mailbox.
QString fromAce(const QByteArray &domain)
void truncate(int position)
Comma not allowed here.
Definition: kemailaddress.h:60
KCODECS_EXPORT EmailParseResult isValidAddress(const QString &aStr)
Validates an email address in the form of "Joe User" [email protected]
QByteArray trimmed() const const
KCODECS_EXPORT QString normalizeAddressesAndDecodeIdn(const QString &addresses)
Normalizes all email addresses in the given list and decodes all IDNs.
QString & prepend(QChar ch)
KCODECS_EXPORT EmailParseResult splitAddress(const QByteArray &address, QByteArray &displayName, QByteArray &addrSpec, QByteArray &comment)
Splits the given address into display name, email address and comment.
The address is empty.
Definition: kemailaddress.h:53
KCODECS_EXPORT QString decodeRFC2047String(const QString &text)
Decodes string text according to RFC2047, i.e., the construct =?charset?[qb]?encoded?=.
Definition: kcodecs.cpp:351
bool isEmpty() const const
QString simplified() const const
KCODECS_EXPORT bool isValidSimpleAddress(const QString &aStr)
Validates an email address in the form of [email protected]
QString join(const QString &separator) const const
QString & remove(int position, int n)
QString tr(const char *sourceText, const char *disambiguation, int n)
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
Quotes (single or double) not matched.
Definition: kemailaddress.h:63
void clear()
void setPath(const QString &path, QUrl::ParsingMode mode)
This file provides static methods for email address validation.
KCODECS_EXPORT QByteArray firstEmailAddress(const QByteArray &addresses)
Returns the pure email address (addr-spec in RFC2822) of the first email address of a list of address...
KCODECS_EXPORT bool extractEmailAddressAndName(const QString &aStr, QString &mail, QString &name)
Return email address and name from string.
KCODECS_EXPORT QString simpleEmailAddressErrorMsg()
Returns a i18n string to be used in msgboxes.
> with no preceding <
Definition: kemailaddress.h:58
QString fromUtf8(const char *str, int size)
No address specified, only domain.
Definition: kemailaddress.h:62
An invalid displayname detected in address.
Definition: kemailaddress.h:66
QByteArray toAce(const QString &domain)
QString anchoredPattern(const QString &expression)
bool isEmpty() const const
No domain in address.
Definition: kemailaddress.h:56
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void setScheme(const QString &scheme)
QString path(QUrl::ComponentFormattingOptions options) const const
KCODECS_EXPORT QString normalizeAddressesAndEncodeIdn(const QString &str)
Normalizes all email addresses in the given list and encodes all IDNs in punycode.
bool hasMatch() const const
KCODECS_EXPORT EmailParseResult isValidAddressList(const QString &aStr, QString &badAddr)
Validates a list of email addresses, and also allow aliases and distribution lists to be expanded bef...
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
KCODECS_EXPORT QString emailParseResultToString(EmailParseResult errorCode)
Translate the enum errorcodes from emailParseResult into i18n&#39;d strings that can be used for msg boxe...
void truncate(int pos)
KCODECS_EXPORT QByteArray encodeRFC2047String(const QString &src, const QByteArray &charset)
Encodes string src according to RFC2047 using charset charset.
Definition: kcodecs.cpp:415
Email is valid.
Definition: kemailaddress.h:52
An invalid character detected in address.
Definition: kemailaddress.h:65
QString scheme() const const
KCODECS_EXPORT QString toIdn(const QString &addrSpec)
Encodes the domain part of the given addr-spec in punycode if it&#39;s an IDN.
More than one @ in address.
Definition: kemailaddress.h:59
QList::iterator end()
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
Returns the pure email address (addr-spec in RFC2822) of the given address (mailbox in RFC2822)...
char toLatin1() const const
KCODECS_EXPORT QStringList splitAddressList(const QString &aStr)
Split a comma separated list of email addresses.
KCODECS_EXPORT QString fromIdn(const QString &addrSpec)
Decodes the punycode domain part of the given addr-spec if it&#39;s an IDN.
Missing @ in address.
Definition: kemailaddress.h:61
QByteArray toLatin1() const const
QString mid(int position, int n) const const
KCODECS_EXPORT QString normalizedAddress(const QString &displayName, const QString &addrSpec, const QString &comment=QString())
Returns a normalized address built from the given parts.
int count() const const
KCODECS_EXPORT QString quoteNameIfNecessary(const QString &str)
Add quote characters around the given string if it contains a character that makes that necessary...
typedef ConstIterator
int length() const const
void reserve(int size)
char * data()
QString left(int n) const const
Something is unbalanced.
Definition: kemailaddress.h:54
EmailParseResult
Email validation result.
Definition: kemailaddress.h:51
KCODECS_EXPORT QString decodeMailtoUrl(const QUrl &mailtoUrl)
Extracts the mailbox out of the mailto: URL.
QList::iterator begin()
KCODECS_EXPORT bool compareEmail(const QString &email1, const QString &email2, bool matchName)
Compare two email addresses.
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Jun 5 2020 22:47:13 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.