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

kpimutils

  • 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( "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  // FIXME count only @ not in double quotes
329 
330  bool tooManyAtsFlag = false;
331 
332  int atCount = aStr.count( '@' );
333  if ( atCount > 1 ) {
334  tooManyAtsFlag = true;
335  } else if ( atCount == 0 ) {
336  return TooFewAts;
337  }
338 
339  // The main parser, try and catch all weird and wonderful
340  // mistakes users and/or machines can create
341 
342  enum {
343  TopLevel,
344  InComment,
345  InAngleAddress
346  } context = TopLevel;
347  bool inQuotedString = false;
348  int commentLevel = 0;
349 
350  unsigned int strlen = aStr.length();
351 
352  for ( unsigned int index = 0; index < strlen; index++ ) {
353  switch ( context ) {
354  case TopLevel :
355  {
356  switch ( aStr[index].toLatin1() ) {
357  case '"' :
358  inQuotedString = !inQuotedString;
359  break;
360  case '(' :
361  if ( !inQuotedString ) {
362  context = InComment;
363  commentLevel = 1;
364  }
365  break;
366  case '[' :
367  if ( !inQuotedString ) {
368  return InvalidDisplayName;
369  }
370  break;
371  case ']' :
372  if ( !inQuotedString ) {
373  return InvalidDisplayName;
374  }
375  break;
376  case ':' :
377  if ( !inQuotedString ) {
378  return DisallowedChar;
379  }
380  break;
381  case '<' :
382  if ( !inQuotedString ) {
383  context = InAngleAddress;
384  }
385  break;
386  case '\\' : // quoted character
387  ++index; // skip the '\'
388  if ( ( index + 1 ) > strlen ) {
389  return UnexpectedEnd;
390  }
391  break;
392  case ',' :
393  if ( !inQuotedString ) {
394  return UnexpectedComma;
395  }
396  break;
397  case ')' :
398  if ( !inQuotedString ) {
399  return UnbalancedParens;
400  }
401  break;
402  case '>' :
403  if ( !inQuotedString ) {
404  return UnopenedAngleAddr;
405  }
406  break;
407  case '@' :
408  if ( !inQuotedString ) {
409  if ( index == 0 ) { // Missing local part
410  return MissingLocalPart;
411  } else if ( index == strlen-1 ) {
412  return MissingDomainPart;
413  }
414  } else if ( inQuotedString ) {
415  --atCount;
416  if ( atCount == 1 ) {
417  tooManyAtsFlag = false;
418  }
419  }
420  break;
421  }
422  break;
423  }
424  case InComment :
425  {
426  switch ( aStr[index].toLatin1() ) {
427  case '(' :
428  ++commentLevel;
429  break;
430  case ')' :
431  --commentLevel;
432  if ( commentLevel == 0 ) {
433  context = TopLevel;
434  }
435  break;
436  case '\\' : // quoted character
437  ++index; // skip the '\'
438  if ( ( index + 1 ) > strlen ) {
439  return UnexpectedEnd;
440  }
441  break;
442  }
443  break;
444  }
445 
446  case InAngleAddress :
447  {
448  switch ( aStr[index].toLatin1() ) {
449  case ',' :
450  if ( !inQuotedString ) {
451  return UnexpectedComma;
452  }
453  break;
454  case '"' :
455  inQuotedString = !inQuotedString;
456  break;
457  case '@' :
458  if ( inQuotedString ) {
459  --atCount;
460  if ( atCount == 1 ) {
461  tooManyAtsFlag = false;
462  }
463  }
464  break;
465  case '>' :
466  if ( !inQuotedString ) {
467  context = TopLevel;
468  break;
469  }
470  break;
471  case '\\' : // quoted character
472  ++index; // skip the '\'
473  if ( ( index + 1 ) > strlen ) {
474  return UnexpectedEnd;
475  }
476  break;
477  }
478  break;
479  }
480  }
481  }
482 
483  if ( atCount == 0 && !inQuotedString ) {
484  return TooFewAts;
485  }
486 
487  if ( inQuotedString ) {
488  return UnbalancedQuote;
489  }
490 
491  if ( context == InComment ) {
492  return UnbalancedParens;
493  }
494 
495  if ( context == InAngleAddress ) {
496  return UnclosedAngleAddr;
497  }
498 
499  if ( tooManyAtsFlag ) {
500  return TooManyAts;
501  }
502 
503  return AddressOk;
504 }
505 
506 //-----------------------------------------------------------------------------
507 KPIMUtils::EmailParseResult KPIMUtils::isValidAddressList( const QString &aStr,
508  QString &badAddr )
509 {
510  if ( aStr.isEmpty() ) {
511  return AddressEmpty;
512  }
513 
514  const QStringList list = splitAddressList( aStr );
515 
516  QStringList::const_iterator it = list.begin();
517  EmailParseResult errorCode = AddressOk;
518  for ( it = list.begin(); it != list.end(); ++it ) {
519  errorCode = isValidAddress( *it );
520  if ( errorCode != AddressOk ) {
521  badAddr = ( *it );
522  break;
523  }
524  }
525  return errorCode;
526 }
527 
528 //-----------------------------------------------------------------------------
529 QString KPIMUtils::emailParseResultToString( EmailParseResult errorCode )
530 {
531  switch ( errorCode ) {
532  case TooManyAts :
533  return i18n( "The email address you entered is not valid because it "
534  "contains more than one @. "
535  "You will not create valid messages if you do not "
536  "change your address." );
537  case TooFewAts :
538  return i18n( "The email address you entered is not valid because it "
539  "does not contain a @. "
540  "You will not create valid messages if you do not "
541  "change your address." );
542  case AddressEmpty :
543  return i18n( "You have to enter something in the email address field." );
544  case MissingLocalPart :
545  return i18n( "The email address you entered is not valid because it "
546  "does not contain a local part." );
547  case MissingDomainPart :
548  return i18n( "The email address you entered is not valid because it "
549  "does not contain a domain part." );
550  case UnbalancedParens :
551  return i18n( "The email address you entered is not valid because it "
552  "contains unclosed comments/brackets." );
553  case AddressOk :
554  return i18n( "The email address you entered is valid." );
555  case UnclosedAngleAddr :
556  return i18n( "The email address you entered is not valid because it "
557  "contains an unclosed angle bracket." );
558  case UnopenedAngleAddr :
559  return i18n( "The email address you entered is not valid because it "
560  "contains too many closing angle brackets." );
561  case UnexpectedComma :
562  return i18n( "The email address you have entered is not valid because it "
563  "contains an unexpected comma." );
564  case UnexpectedEnd :
565  return i18n( "The email address you entered is not valid because it ended "
566  "unexpectedly. This probably means you have used an escaping "
567  "type character like a '\\' as the last character in your "
568  "email address." );
569  case UnbalancedQuote :
570  return i18n( "The email address you entered is not valid because it "
571  "contains quoted text which does not end." );
572  case NoAddressSpec :
573  return i18n( "The email address you entered is not valid because it "
574  "does not seem to contain an actual email address, i.e. "
575  "something of the form joe@example.org." );
576  case DisallowedChar :
577  return i18n( "The email address you entered is not valid because it "
578  "contains an illegal character." );
579  case InvalidDisplayName :
580  return i18n( "The email address you have entered is not valid because it "
581  "contains an invalid display name." );
582  }
583  return i18n( "Unknown problem with email address" );
584 }
585 
586 //-----------------------------------------------------------------------------
587 bool KPIMUtils::isValidSimpleAddress( const QString &aStr )
588 {
589  // If we are passed an empty string bail right away no need to process further
590  // and waste resources
591  if ( aStr.isEmpty() ) {
592  return false;
593  }
594 
595  int atChar = aStr.lastIndexOf( '@' );
596  QString domainPart = aStr.mid( atChar + 1 );
597  QString localPart = aStr.left( atChar );
598 
599  // Both of these parts must be non empty
600  // after all we cannot have emails like:
601  // @kde.org, or foo@
602  if ( localPart.isEmpty() || domainPart.isEmpty() ) {
603  return false;
604  }
605 
606  bool tooManyAtsFlag = false;
607  bool inQuotedString = false;
608  int atCount = localPart.count( '@' );
609 
610  unsigned int strlen = localPart.length();
611  for ( unsigned int index = 0; index < strlen; index++ ) {
612  switch ( localPart[ index ].toLatin1() ) {
613  case '"' :
614  inQuotedString = !inQuotedString;
615  break;
616  case '@' :
617  if ( inQuotedString ) {
618  --atCount;
619  if ( atCount == 0 ) {
620  tooManyAtsFlag = false;
621  }
622  }
623  break;
624  }
625  }
626 
627  QString addrRx;
628 
629  if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) {
630  addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@";
631  } else {
632  addrRx = "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
633  }
634  if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) {
635  addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]";
636  } else {
637  addrRx += "[\\w-#]+(\\.[\\w-#]+)*";
638  }
639  QRegExp rx( addrRx );
640  return rx.exactMatch( aStr ) && !tooManyAtsFlag;
641 }
642 
643 //-----------------------------------------------------------------------------
644 QString KPIMUtils::simpleEmailAddressErrorMsg()
645 {
646  return i18n( "The email address you entered is not valid because it "
647  "does not seem to contain an actual email address, i.e. "
648  "something of the form joe@example.org." );
649 }
650 
651 //-----------------------------------------------------------------------------
652 QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address )
653 {
654  QByteArray dummy1, dummy2, addrSpec;
655  EmailParseResult result =
656  splitAddressInternal( address, dummy1, addrSpec, dummy2,
657  false/* don't allow multiple addresses */ );
658  if ( result != AddressOk ) {
659  addrSpec = QByteArray();
660  if ( result != AddressEmpty ) {
661  kDebug()
662  << "Input:" << address << "\nError:"
663  << emailParseResultToString( result );
664  }
665  }
666 
667  return addrSpec;
668 }
669 
670 //-----------------------------------------------------------------------------
671 QString KPIMUtils::extractEmailAddress( const QString &address )
672 {
673  return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) );
674 }
675 
676 //-----------------------------------------------------------------------------
677 QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses )
678 {
679  QByteArray dummy1, dummy2, addrSpec;
680  EmailParseResult result =
681  splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
682  true/* allow multiple addresses */ );
683  if ( result != AddressOk ) {
684  addrSpec = QByteArray();
685  if ( result != AddressEmpty ) {
686  kDebug()
687  << "Input: aStr\nError:"
688  << emailParseResultToString( result );
689  }
690  }
691 
692  return addrSpec;
693 }
694 
695 //-----------------------------------------------------------------------------
696 QString KPIMUtils::firstEmailAddress( const QString &addresses )
697 {
698  return QString::fromUtf8( firstEmailAddress( addresses.toUtf8() ) );
699 }
700 
701 //-----------------------------------------------------------------------------
702 bool KPIMUtils::extractEmailAddressAndName( const QString &aStr,
703  QString &mail, QString &name )
704 {
705  name.clear();
706  mail.clear();
707 
708  const int len = aStr.length();
709  const char cQuotes = '"';
710 
711  bool bInComment = false;
712  bool bInQuotesOutsideOfEmail = false;
713  int i = 0, iAd = 0, iMailStart = 0, iMailEnd = 0;
714  QChar c;
715  unsigned int commentstack = 0;
716 
717  // Find the '@' of the email address
718  // skipping all '@' inside "(...)" comments:
719  while ( i < len ) {
720  c = aStr[i];
721  if ( '(' == c ) {
722  commentstack++;
723  }
724  if ( ')' == c ) {
725  commentstack--;
726  }
727  bInComment = commentstack != 0;
728  if ( '"' == c && !bInComment ) {
729  bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
730  }
731 
732  if ( !bInComment && !bInQuotesOutsideOfEmail ) {
733  if ( '@' == c ) {
734  iAd = i;
735  break; // found it
736  }
737  }
738  ++i;
739  }
740 
741  if ( !iAd ) {
742  // We suppose the user is typing the string manually and just
743  // has not finished typing the mail address part.
744  // So we take everything that's left of the '<' as name and the rest as mail
745  for ( i = 0; len > i; ++i ) {
746  c = aStr[i];
747  if ( '<' != c ) {
748  name.append( c );
749  } else {
750  break;
751  }
752  }
753  mail = aStr.mid( i + 1 );
754  if ( mail.endsWith( '>' ) ) {
755  mail.truncate( mail.length() - 1 );
756  }
757 
758  } else {
759  // Loop backwards until we find the start of the string
760  // or a ',' that is outside of a comment
761  // and outside of quoted text before the leading '<'.
762  bInComment = false;
763  bInQuotesOutsideOfEmail = false;
764  for ( i = iAd-1; 0 <= i; --i ) {
765  c = aStr[i];
766  if ( bInComment ) {
767  if ( '(' == c ) {
768  if ( !name.isEmpty() ) {
769  name.prepend( ' ' );
770  }
771  bInComment = false;
772  } else {
773  name.prepend( c ); // all comment stuff is part of the name
774  }
775  } else if ( bInQuotesOutsideOfEmail ) {
776  if ( cQuotes == c ) {
777  bInQuotesOutsideOfEmail = false;
778  } else if ( c != '\\' ) {
779  name.prepend( c );
780  }
781  } else {
782  // found the start of this addressee ?
783  if ( ',' == c ) {
784  break;
785  }
786  // stuff is before the leading '<' ?
787  if ( iMailStart ) {
788  if ( cQuotes == c ) {
789  bInQuotesOutsideOfEmail = true; // end of quoted text found
790  } else {
791  name.prepend( c );
792  }
793  } else {
794  switch ( c.toLatin1() ) {
795  case '<':
796  iMailStart = i;
797  break;
798  case ')':
799  if ( !name.isEmpty() ) {
800  name.prepend( ' ' );
801  }
802  bInComment = true;
803  break;
804  default:
805  if ( ' ' != c ) {
806  mail.prepend( c );
807  }
808  }
809  }
810  }
811  }
812 
813  name = name.simplified();
814  mail = mail.simplified();
815 
816  if ( mail.isEmpty() ) {
817  return false;
818  }
819 
820  mail.append( '@' );
821 
822  // Loop forward until we find the end of the string
823  // or a ',' that is outside of a comment
824  // and outside of quoted text behind the trailing '>'.
825  bInComment = false;
826  bInQuotesOutsideOfEmail = false;
827  int parenthesesNesting = 0;
828  for ( i = iAd+1; len > i; ++i ) {
829  c = aStr[i];
830  if ( bInComment ) {
831  if ( ')' == c ) {
832  if ( --parenthesesNesting == 0 ) {
833  bInComment = false;
834  if ( !name.isEmpty() ) {
835  name.append( ' ' );
836  }
837  } else {
838  // nested ")", add it
839  name.append( ')' ); // name can't be empty here
840  }
841  } else {
842  if ( '(' == c ) {
843  // nested "("
844  ++parenthesesNesting;
845  }
846  name.append( c ); // all comment stuff is part of the name
847  }
848  } else if ( bInQuotesOutsideOfEmail ) {
849  if ( cQuotes == c ) {
850  bInQuotesOutsideOfEmail = false;
851  } else if ( c != '\\' ) {
852  name.append( c );
853  }
854  } else {
855  // found the end of this addressee ?
856  if ( ',' == c ) {
857  break;
858  }
859  // stuff is behind the trailing '>' ?
860  if ( iMailEnd ) {
861  if ( cQuotes == c ) {
862  bInQuotesOutsideOfEmail = true; // start of quoted text found
863  } else {
864  name.append( c );
865  }
866  } else {
867  switch ( c.toLatin1() ) {
868  case '>':
869  iMailEnd = i;
870  break;
871  case '(':
872  if ( !name.isEmpty() ) {
873  name.append( ' ' );
874  }
875  if ( ++parenthesesNesting > 0 ) {
876  bInComment = true;
877  }
878  break;
879  default:
880  if ( ' ' != c ) {
881  mail.append( c );
882  }
883  }
884  }
885  }
886  }
887  }
888 
889  name = name.simplified();
890  mail = mail.simplified();
891 
892  return ! ( name.isEmpty() || mail.isEmpty() );
893 }
894 
895 //-----------------------------------------------------------------------------
896 bool KPIMUtils::compareEmail( const QString &email1, const QString &email2,
897  bool matchName )
898 {
899  QString e1Name, e1Email, e2Name, e2Email;
900 
901  extractEmailAddressAndName( email1, e1Email, e1Name );
902  extractEmailAddressAndName( email2, e2Email, e2Name );
903 
904  return e1Email == e2Email &&
905  ( !matchName || ( e1Name == e2Name ) );
906 }
907 
908 //-----------------------------------------------------------------------------
909 QString KPIMUtils::normalizedAddress( const QString &displayName,
910  const QString &addrSpec,
911  const QString &comment )
912 {
913  const QString realDisplayName = KMime::removeBidiControlChars( displayName );
914  if ( realDisplayName.isEmpty() && comment.isEmpty() ) {
915  return addrSpec;
916  } else if ( comment.isEmpty() ) {
917  if ( !realDisplayName.startsWith( '\"' ) ) {
918  return quoteNameIfNecessary( realDisplayName ) + " <" + addrSpec + '>';
919  } else {
920  return realDisplayName + " <" + addrSpec + '>';
921  }
922  } else if ( realDisplayName.isEmpty() ) {
923  QString commentStr = comment;
924  return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + '>';
925  } else {
926  return realDisplayName + " (" + comment + ") <" + addrSpec + '>';
927  }
928 }
929 
930 //-----------------------------------------------------------------------------
931 QString KPIMUtils::fromIdn( const QString &addrSpec )
932 {
933  const int atPos = addrSpec.lastIndexOf( '@' );
934  if ( atPos == -1 ) {
935  return addrSpec;
936  }
937 
938  QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() );
939  if ( idn.isEmpty() ) {
940  return QString();
941  }
942 
943  return addrSpec.left( atPos + 1 ) + idn;
944 }
945 
946 //-----------------------------------------------------------------------------
947 QString KPIMUtils::toIdn( const QString &addrSpec )
948 {
949  const int atPos = addrSpec.lastIndexOf( '@' );
950  if ( atPos == -1 ) {
951  return addrSpec;
952  }
953 
954  QString idn = KUrl::toAce( addrSpec.mid( atPos + 1 ) );
955  if ( idn.isEmpty() ) {
956  return addrSpec;
957  }
958 
959  return addrSpec.left( atPos + 1 ) + idn;
960 }
961 
962 //-----------------------------------------------------------------------------
963 QString KPIMUtils::normalizeAddressesAndDecodeIdn( const QString &str )
964 {
965  // kDebug() << str;
966  if ( str.isEmpty() ) {
967  return str;
968  }
969 
970  const QStringList addressList = splitAddressList( str );
971  QStringList normalizedAddressList;
972 
973  QByteArray displayName, addrSpec, comment;
974 
975  for ( QStringList::ConstIterator it = addressList.begin();
976  ( it != addressList.end() );
977  ++it ) {
978  if ( !( *it ).isEmpty() ) {
979  if ( splitAddress( ( *it ).toUtf8(),
980  displayName, addrSpec, comment ) == AddressOk ) {
981 
982  displayName = KMime::decodeRFC2047String( displayName ).toUtf8();
983  comment = KMime::decodeRFC2047String( comment ).toUtf8();
984 
985  normalizedAddressList
986  << normalizedAddress( QString::fromUtf8( displayName ),
987  fromIdn( QString::fromUtf8( addrSpec ) ),
988  QString::fromUtf8( comment ) );
989  }
990  }
991  }
992  /*
993  kDebug() << "normalizedAddressList: \""
994  << normalizedAddressList.join( ", " )
995  << "\"";
996  */
997  return normalizedAddressList.join( ", " );
998 }
999 
1000 //-----------------------------------------------------------------------------
1001 QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str )
1002 {
1003  //kDebug() << str;
1004  if ( str.isEmpty() ) {
1005  return str;
1006  }
1007 
1008  const QStringList addressList = splitAddressList( str );
1009  QStringList normalizedAddressList;
1010 
1011  QByteArray displayName, addrSpec, comment;
1012 
1013  for ( QStringList::ConstIterator it = addressList.begin();
1014  ( it != addressList.end() );
1015  ++it ) {
1016  if ( !( *it ).isEmpty() ) {
1017  if ( splitAddress( ( *it ).toUtf8(),
1018  displayName, addrSpec, comment ) == AddressOk ) {
1019 
1020  normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ),
1021  toIdn( QString::fromUtf8( addrSpec ) ),
1022  QString::fromUtf8( comment ) );
1023  }
1024  }
1025  }
1026 
1027  /*
1028  kDebug() << "normalizedAddressList: \""
1029  << normalizedAddressList.join( ", " )
1030  << "\"";
1031  */
1032  return normalizedAddressList.join( ", " );
1033 }
1034 
1035 //-----------------------------------------------------------------------------
1036 // Escapes unescaped doublequotes in str.
1037 static QString escapeQuotes( const QString &str )
1038 {
1039  if ( str.isEmpty() ) {
1040  return QString();
1041  }
1042 
1043  QString escaped;
1044  // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
1045  escaped.reserve( 2 * str.length() );
1046  unsigned int len = 0;
1047  for ( int i = 0; i < str.length(); ++i, ++len ) {
1048  if ( str[i] == '"' ) { // unescaped doublequote
1049  escaped[len] = '\\';
1050  ++len;
1051  } else if ( str[i] == '\\' ) { // escaped character
1052  escaped[len] = '\\';
1053  ++len;
1054  ++i;
1055  if ( i >= str.length() ) { // handle trailing '\' gracefully
1056  break;
1057  }
1058  }
1059  escaped[len] = str[i];
1060  }
1061  escaped.truncate( len );
1062  return escaped;
1063 }
1064 
1065 //-----------------------------------------------------------------------------
1066 QString KPIMUtils::quoteNameIfNecessary( const QString &str )
1067 {
1068  QString quoted = str;
1069 
1070  QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
1071  // avoid double quoting
1072  if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) {
1073  quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\"";
1074  } else if ( quoted.indexOf( needQuotes ) != -1 ) {
1075  quoted = "\"" + escapeQuotes( quoted ) + "\"";
1076  }
1077 
1078  return quoted;
1079 }
1080 
1081 KUrl KPIMUtils::encodeMailtoUrl( const QString &mailbox )
1082 {
1083  const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox, "utf-8" );
1084  KUrl mailtoUrl;
1085  mailtoUrl.setProtocol( "mailto" );
1086  mailtoUrl.setPath( encodedPath );
1087  return mailtoUrl;
1088 }
1089 
1090 QString KPIMUtils::decodeMailtoUrl( const KUrl &mailtoUrl )
1091 {
1092  Q_ASSERT( mailtoUrl.protocol().toLower() == "mailto" );
1093  return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() );
1094 }
KPIMUtils::isValidAddress
EmailParseResult isValidAddress(const QString &aStr)
Validates an email address in the form of &quot;Joe User&quot; joe@example.org.
Definition: email.cpp:315
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:1066
KPIMUtils::isValidSimpleAddress
bool isValidSimpleAddress(const QString &aStr)
Validates an email address in the form of joe@example.org.
Definition: email.cpp:587
KPIMUtils::normalizeAddressesAndEncodeIdn
QString normalizeAddressesAndEncodeIdn(const QString &str)
Normalizes all email addresses in the given list and encodes all IDNs in punycode.
Definition: email.cpp:1001
KPIMUtils::emailParseResultToString
QString emailParseResultToString(EmailParseResult errorCode)
Translate the enum errorcodes from emailParseResult into i18n&#39;d strings that can be used for msg boxe...
Definition: email.cpp:529
KPIMUtils::UnbalancedParens
Unbalanced ( )
Definition: email.h:67
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
KPIMUtils::UnexpectedEnd
Something is unbalanced.
Definition: email.h:66
KPIMUtils::TooFewAts
Missing @ in address.
Definition: email.h:73
KPIMUtils::MissingLocalPart
No address specified, only domain.
Definition: email.h:74
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
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&#39;s an IDN.
Definition: email.cpp:947
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:507
KPIMUtils::UnexpectedComma
Comma not allowed here.
Definition: email.h:72
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:652
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:909
KPIMUtils::fromIdn
QString fromIdn(const QString &addrSpec)
Decodes the punycode domain part of the given addr-spec if it&#39;s an IDN.
Definition: email.cpp:931
KPIMUtils::AddressEmpty
The address is empty.
Definition: email.h:65
KPIMUtils::AddressOk
Email is valid.
Definition: email.h:64
KPIMUtils::UnopenedAngleAddr
&gt; with no preceding &lt;
Definition: email.h:70
KPIMUtils::normalizeAddressesAndDecodeIdn
QString normalizeAddressesAndDecodeIdn(const QString &addresses)
Normalizes all email addresses in the given list and decodes all IDNs.
Definition: email.cpp:963
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:677
KPIMUtils::simpleEmailAddressErrorMsg
QString simpleEmailAddressErrorMsg()
Returns a i18n string to be used in msgboxes.
Definition: email.cpp:644
KPIMUtils::compareEmail
bool compareEmail(const QString &email1, const QString &email2, bool matchName)
Compare two email addresses.
Definition: email.cpp:896
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
KPIMUtils::extractEmailAddressAndName
bool extractEmailAddressAndName(const QString &aStr, QString &mail, QString &name)
Return email address and name from string.
Definition: email.cpp:702
KPIMUtils::UnclosedAngleAddr
&lt; with no matching &gt;
Definition: email.h:69
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Tue Nov 26 2013 09:02:35 by doxygen 1.8.5 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

kdepimlibs-4.11.3 API Reference

Skip menu "kdepimlibs-4.11.3 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
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