libemailfunctions

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