• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.4 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 <KLocale>
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  break;
414  }
415  } else if ( inQuotedString ) {
416  --atCount;
417  if ( atCount == 1 ) {
418  tooManyAtsFlag = false;
419  }
420  }
421  break;
422  }
423  break;
424  }
425  case InComment :
426  {
427  switch ( aStr[index].toLatin1() ) {
428  case '(' :
429  ++commentLevel;
430  break;
431  case ')' :
432  --commentLevel;
433  if ( commentLevel == 0 ) {
434  context = TopLevel;
435  }
436  break;
437  case '\\' : // quoted character
438  ++index; // skip the '\'
439  if ( ( index + 1 ) > strlen ) {
440  return UnexpectedEnd;
441  }
442  break;
443  }
444  break;
445  }
446 
447  case InAngleAddress :
448  {
449  switch ( aStr[index].toLatin1() ) {
450  case ',' :
451  if ( !inQuotedString ) {
452  return UnexpectedComma;
453  }
454  break;
455  case '"' :
456  inQuotedString = !inQuotedString;
457  break;
458  case '@' :
459  if ( inQuotedString ) {
460  --atCount;
461  if ( atCount == 1 ) {
462  tooManyAtsFlag = false;
463  }
464  }
465  break;
466  case '>' :
467  if ( !inQuotedString ) {
468  context = TopLevel;
469  break;
470  }
471  break;
472  case '\\' : // quoted character
473  ++index; // skip the '\'
474  if ( ( index + 1 ) > strlen ) {
475  return UnexpectedEnd;
476  }
477  break;
478  }
479  break;
480  }
481  }
482  }
483 
484  if ( atCount == 0 && !inQuotedString ) {
485  return TooFewAts;
486  }
487 
488  if ( inQuotedString ) {
489  return UnbalancedQuote;
490  }
491 
492  if ( context == InComment ) {
493  return UnbalancedParens;
494  }
495 
496  if ( context == InAngleAddress ) {
497  return UnclosedAngleAddr;
498  }
499 
500  if ( tooManyAtsFlag ) {
501  return TooManyAts;
502  }
503 
504  return AddressOk;
505 }
506 
507 //-----------------------------------------------------------------------------
508 KPIMUtils::EmailParseResult KPIMUtils::isValidAddressList( const QString &aStr,
509  QString &badAddr )
510 {
511  if ( aStr.isEmpty() ) {
512  return AddressEmpty;
513  }
514 
515  const QStringList list = splitAddressList( aStr );
516 
517  QStringList::const_iterator it = list.begin();
518  EmailParseResult errorCode = AddressOk;
519  for ( it = list.begin(); it != list.end(); ++it ) {
520  errorCode = isValidAddress( *it );
521  if ( errorCode != AddressOk ) {
522  badAddr = ( *it );
523  break;
524  }
525  }
526  return errorCode;
527 }
528 
529 //-----------------------------------------------------------------------------
530 QString KPIMUtils::emailParseResultToString( EmailParseResult errorCode )
531 {
532  switch ( errorCode ) {
533  case TooManyAts :
534  return i18n( "The email address you entered is not valid because it "
535  "contains more than one @. "
536  "You will not create valid messages if you do not "
537  "change your address." );
538  case TooFewAts :
539  return i18n( "The email address you entered is not valid because it "
540  "does not contain a @. "
541  "You will not create valid messages if you do not "
542  "change your address." );
543  case AddressEmpty :
544  return i18n( "You have to enter something in the email address field." );
545  case MissingLocalPart :
546  return i18n( "The email address you entered is not valid because it "
547  "does not contain a local part." );
548  case MissingDomainPart :
549  return i18n( "The email address you entered is not valid because it "
550  "does not contain a domain part." );
551  case UnbalancedParens :
552  return i18n( "The email address you entered is not valid because it "
553  "contains unclosed comments/brackets." );
554  case AddressOk :
555  return i18n( "The email address you entered is valid." );
556  case UnclosedAngleAddr :
557  return i18n( "The email address you entered is not valid because it "
558  "contains an unclosed angle bracket." );
559  case UnopenedAngleAddr :
560  return i18n( "The email address you entered is not valid because it "
561  "contains too many closing angle brackets." );
562  case UnexpectedComma :
563  return i18n( "The email address you have entered is not valid because it "
564  "contains an unexpected comma." );
565  case UnexpectedEnd :
566  return i18n( "The email address you entered is not valid because it ended "
567  "unexpectedly. This probably means you have used an escaping "
568  "type character like a '\\' as the last character in your "
569  "email address." );
570  case UnbalancedQuote :
571  return i18n( "The email address you entered is not valid because it "
572  "contains quoted text which does not end." );
573  case NoAddressSpec :
574  return i18n( "The email address you entered is not valid because it "
575  "does not seem to contain an actual email address, i.e. "
576  "something of the form joe@example.org." );
577  case DisallowedChar :
578  return i18n( "The email address you entered is not valid because it "
579  "contains an illegal character." );
580  case InvalidDisplayName :
581  return i18n( "The email address you have entered is not valid because it "
582  "contains an invalid display name." );
583  }
584  return i18n( "Unknown problem with email address" );
585 }
586 
587 //-----------------------------------------------------------------------------
588 bool KPIMUtils::isValidSimpleAddress( const QString &aStr )
589 {
590  // If we are passed an empty string bail right away no need to process further
591  // and waste resources
592  if ( aStr.isEmpty() ) {
593  return false;
594  }
595 
596  int atChar = aStr.lastIndexOf( '@' );
597  QString domainPart = aStr.mid( atChar + 1 );
598  QString localPart = aStr.left( atChar );
599 
600  // Both of these parts must be non empty
601  // after all we cannot have emails like:
602  // @kde.org, or foo@
603  if ( localPart.isEmpty() || domainPart.isEmpty() ) {
604  return false;
605  }
606 
607  bool tooManyAtsFlag = false;
608  bool inQuotedString = false;
609  int atCount = localPart.count( '@' );
610 
611  unsigned int strlen = localPart.length();
612  for ( unsigned int index=0; index < strlen; index++ ) {
613  switch( localPart[ index ].toLatin1() ) {
614  case '"' :
615  inQuotedString = !inQuotedString;
616  break;
617  case '@' :
618  if ( inQuotedString ) {
619  --atCount;
620  if ( atCount == 0 ) {
621  tooManyAtsFlag = false;
622  }
623  }
624  break;
625  }
626  }
627 
628  QString addrRx =
629  "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
630 
631  if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) {
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 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Dec 10 2012 13:47:27 by doxygen 1.8.1.2 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.9.4 API Reference

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • 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