• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdepimlibs API Reference
  • KDE Home
  • Contact Us
 

kpimutils

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

KDE's Doxygen guidelines are available online.

kpimutils

Skip menu "kpimutils"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Modules
  • Related Pages

kdepimlibs API Reference

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal