libemailfunctions

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