libemailfunctions

email.cpp
00001 /*  -*- mode: C++; c-file-style: "gnu" -*-
00002 
00003     This file is part of tdepim.
00004     Copyright (c) 2004 TDEPIM developers
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Library General Public
00008     License as published by the Free Software Foundation; either
00009     version 2 of the License, or (at your option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Library General Public License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 #include "email.h"
00022 
00023 #include <kdebug.h>
00024 #include <tdelocale.h>
00025 #include <kidna.h>
00026 #include <kmime_util.h>
00027 
00028 #include <tqregexp.h>
00029 
00030 //-----------------------------------------------------------------------------
00031 TQStringList KPIM::splitEmailAddrList(const TQString& aStr)
00032 {
00033   // Features:
00034   // - always ignores quoted characters
00035   // - ignores everything (including parentheses and commas)
00036   //   inside quoted strings
00037   // - supports nested comments
00038   // - ignores everything (including double quotes and commas)
00039   //   inside comments
00040 
00041   TQStringList list;
00042 
00043   if (aStr.isEmpty())
00044     return list;
00045 
00046   TQString addr;
00047   uint addrstart = 0;
00048   int commentlevel = 0;
00049   bool insidequote = false;
00050 
00051   for (uint index=0; index<aStr.length(); index++) {
00052     // the following conversion to latin1 is o.k. because
00053     // we can safely ignore all non-latin1 characters
00054     switch (aStr[index].latin1()) {
00055     case '"' : // start or end of quoted string
00056       if (commentlevel == 0)
00057         insidequote = !insidequote;
00058       break;
00059     case '(' : // start of comment
00060       if (!insidequote)
00061         commentlevel++;
00062       break;
00063     case ')' : // end of comment
00064       if (!insidequote) {
00065         if (commentlevel > 0)
00066           commentlevel--;
00067         else {
00068           kdDebug(5300) << "Error in address splitting: Unmatched ')'"
00069                         << endl;
00070           return list;
00071         }
00072       }
00073       break;
00074     case '\\' : // quoted character
00075       index++; // ignore the quoted character
00076       break;
00077     case ',' :
00078     case ';' :
00079       if (!insidequote && (commentlevel == 0)) {
00080         addr = aStr.mid(addrstart, index-addrstart);
00081         if (!addr.isEmpty())
00082           list += addr.simplifyWhiteSpace();
00083         addrstart = index+1;
00084       }
00085       break;
00086     }
00087   }
00088   // append the last address to the list
00089   if (!insidequote && (commentlevel == 0)) {
00090     addr = aStr.mid(addrstart, aStr.length()-addrstart);
00091     if (!addr.isEmpty())
00092       list += addr.simplifyWhiteSpace();
00093   }
00094   else
00095     kdDebug(5300) << "Error in address splitting: "
00096                   << "Unexpected end of address list"
00097                   << endl;
00098 
00099   return list;
00100 }
00101 
00102 //-----------------------------------------------------------------------------
00103 // Used by KPIM::splitAddress(...) and KPIM::getFirstEmailAddress(...).
00104 KPIM::EmailParseResult splitAddressInternal( const TQCString& address,
00105                                              TQCString & displayName,
00106                                              TQCString & addrSpec,
00107                                              TQCString & comment,
00108                                              bool allowMultipleAddresses )
00109 {
00110 //  kdDebug() << "KMMessage::splitAddress( " << address << " )" << endl;
00111 
00112   displayName = "";
00113   addrSpec = "";
00114   comment = "";
00115   
00116   // these strings are later copied to displayName resp. addrSpec resp. comment
00117   // we don't operate directly on those variables, since as ByteArray deriverates
00118   // they have a miserable performance on operator+
00119   TQString dName;
00120   TQString aSpec;
00121   TQString cmmt;
00122   
00123   if ( address.isEmpty() )
00124     return KPIM::AddressEmpty;
00125 
00126   // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
00127   // The purpose is to extract a displayable string from the mailboxes.
00128   // Comments in the addr-spec are not handled. No error checking is done.
00129 
00130   enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
00131   bool inQuotedString = false;
00132   int commentLevel = 0;
00133   bool stop = false;
00134 
00135   for ( const char* p = address.data(); *p && !stop; ++p ) {
00136     switch ( context ) {
00137     case TopLevel : {
00138       switch ( *p ) {
00139       case '"' : inQuotedString = !inQuotedString;
00140                  dName += *p;
00141                  break;
00142       case '(' : if ( !inQuotedString ) {
00143                    context = InComment;
00144                    commentLevel = 1;
00145                  }
00146                  else
00147                    dName += *p;
00148                  break;
00149       case '<' : if ( !inQuotedString ) {
00150                    context = InAngleAddress;
00151                  }
00152                  else
00153                    dName += *p;
00154                  break;
00155       case '\\' : // quoted character
00156                  dName += *p;
00157                  ++p; // skip the '\'
00158                  if ( *p )
00159                    dName += *p;
00160                  else
00161                    return KPIM::UnexpectedEnd;
00162                  break;
00163           case ',' :
00164           case ';' : if ( !inQuotedString ) {
00165                    if ( allowMultipleAddresses )
00166                      stop = true;
00167                    else
00168                      return KPIM::UnexpectedComma;
00169                  }
00170                  else
00171                    dName += *p;
00172                  break;
00173       default :  dName += *p;
00174       }
00175       break;
00176     }
00177     case InComment : {
00178       switch ( *p ) {
00179       case '(' : ++commentLevel;
00180                  cmmt += *p;
00181                  break;
00182       case ')' : --commentLevel;
00183                  if ( commentLevel == 0 ) {
00184                    context = TopLevel;
00185                    cmmt += ' '; // separate the text of several comments
00186                  }
00187                  else
00188                    cmmt += *p;
00189                  break;
00190       case '\\' : // quoted character
00191                  cmmt += *p;
00192                  ++p; // skip the '\'
00193                  if ( *p )
00194                    cmmt += *p;
00195                  else
00196                    return KPIM::UnexpectedEnd;
00197                  break;
00198       default :  cmmt += *p;
00199       }
00200       break;
00201     }
00202     case InAngleAddress : {
00203       switch ( *p ) {
00204       case '"' : inQuotedString = !inQuotedString;
00205                  aSpec += *p;
00206                  break;
00207       case '>' : if ( !inQuotedString ) {
00208                    context = TopLevel;
00209                  }
00210                  else
00211                    aSpec += *p;
00212                  break;
00213       case '\\' : // quoted character
00214                  aSpec += *p;
00215                  ++p; // skip the '\'
00216                  if ( *p )
00217                    aSpec += *p;
00218                  else
00219                    return KPIM::UnexpectedEnd;
00220                  break;
00221       default :  aSpec += *p;
00222       }
00223       break;
00224     }
00225     } // switch ( context )
00226   }
00227   // check for errors
00228   if ( inQuotedString )
00229     return KPIM::UnbalancedQuote;
00230   if ( context == InComment )
00231     return KPIM::UnbalancedParens;
00232   if ( context == InAngleAddress )
00233     return KPIM::UnclosedAngleAddr;
00234 
00235     
00236   displayName = dName.stripWhiteSpace().latin1();
00237   comment = cmmt.stripWhiteSpace().latin1();
00238   addrSpec = aSpec.stripWhiteSpace().latin1();
00239 
00240   if ( addrSpec.isEmpty() ) {
00241     if ( displayName.isEmpty() )
00242       return KPIM::NoAddressSpec;
00243     else {
00244       addrSpec = displayName;
00245       displayName.truncate( 0 );
00246     }
00247   }
00248 /*
00249   kdDebug() << "display-name : \"" << displayName << "\"" << endl;
00250   kdDebug() << "comment      : \"" << comment << "\"" << endl;
00251   kdDebug() << "addr-spec    : \"" << addrSpec << "\"" << endl;
00252 */
00253   return KPIM::AddressOk;
00254 }
00255 
00256 
00257 //-----------------------------------------------------------------------------
00258 KPIM::EmailParseResult KPIM::splitAddress( const TQCString& address,
00259                                            TQCString & displayName,
00260                                            TQCString & addrSpec,
00261                                            TQCString & comment )
00262 {
00263   return splitAddressInternal( address, displayName, addrSpec, comment,
00264                                false /* don't allow multiple addresses */ );
00265 }
00266 
00267 
00268 //-----------------------------------------------------------------------------
00269 KPIM::EmailParseResult KPIM::splitAddress( const TQString & address,
00270                                            TQString & displayName,
00271                                            TQString & addrSpec,
00272                                            TQString & comment )
00273 {
00274   TQCString d, a, c;
00275   KPIM::EmailParseResult result = splitAddress( address.utf8(), d, a, c );
00276   if ( result == AddressOk ) {
00277     displayName = TQString::fromUtf8( d );
00278     addrSpec = TQString::fromUtf8( a );
00279     comment = TQString::fromUtf8( c );
00280   }
00281   return result;
00282 }
00283 
00284 
00285 //-----------------------------------------------------------------------------
00286 KPIM::EmailParseResult KPIM::isValidEmailAddress( const TQString& aStr )
00287 {
00288   // If we are passed an empty string bail right away no need to process further
00289   // and waste resources
00290   if ( aStr.isEmpty() ) {
00291     return AddressEmpty;
00292   }
00293 
00294   // count how many @'s are in the string that is passed to us
00295   // if 0 or > 1 take action
00296   // at this point to many @'s cannot bail out right away since
00297   // @ is allowed in qoutes, so we use a bool to keep track
00298   // and then make a judgement further down in the parser
00299   // FIXME count only @ not in double quotes
00300 
00301   bool tooManyAtsFlag = false;
00302 
00303   int atCount = aStr.contains('@');
00304   if ( atCount > 1 ) {
00305     tooManyAtsFlag = true;;
00306   } else if ( atCount == 0 ) {
00307       return TooFewAts;
00308   }
00309 
00310   // The main parser, try and catch all weird and wonderful
00311   // mistakes users and/or machines can create
00312 
00313   enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
00314   bool inQuotedString = false;
00315   int commentLevel = 0;
00316 
00317   unsigned int strlen = aStr.length();
00318 
00319   for ( unsigned int index=0; index < strlen; index++ ) {
00320     switch ( context ) {
00321     case TopLevel : {
00322       switch ( aStr[index].latin1() ) {
00323         case '"' : inQuotedString = !inQuotedString;
00324           break;
00325         case '(' :
00326           if ( !inQuotedString ) {
00327             context = InComment;
00328             commentLevel = 1;
00329           }
00330           break;
00331         case '[' :
00332           if ( !inQuotedString ) {
00333             return InvalidDisplayName;
00334           }
00335           break;
00336         case ']' :
00337           if ( !inQuotedString ) {
00338             return InvalidDisplayName;
00339           }
00340           break;
00341         case ':' :
00342           if ( !inQuotedString ) {
00343             return DisallowedChar;
00344           }
00345           break;
00346         case '<' :
00347           if ( !inQuotedString ) {
00348             context = InAngleAddress;
00349           }
00350           break;
00351         case '\\' : // quoted character
00352           ++index; // skip the '\'
00353           if (( index + 1 )> strlen ) {
00354             return UnexpectedEnd;
00355           }
00356           break;
00357             case ',' :
00358             case ';' :
00359           if ( !inQuotedString )
00360             return UnexpectedComma;
00361           break;
00362         case ')' :
00363           if ( !inQuotedString )
00364             return UnbalancedParens;
00365           break;
00366         case '>' :
00367           if ( !inQuotedString )
00368             return UnopenedAngleAddr;
00369           break;
00370         case '@' :
00371           if ( !inQuotedString ) {
00372             if ( index == 0 ) {  // Missing local part
00373               return MissingLocalPart;
00374             } else if( index == strlen-1 ) {
00375               return MissingDomainPart;
00376             }
00377           } else if ( inQuotedString ) {
00378             --atCount;
00379             if ( atCount == 1 ) {
00380               tooManyAtsFlag = false;
00381             }
00382           }
00383           break;
00384       }
00385       break;
00386     }
00387     case InComment : {
00388       switch ( aStr[index] ) {
00389         case '(' : ++commentLevel;
00390           break;
00391         case ')' : --commentLevel;
00392           if ( commentLevel == 0 ) {
00393             context = TopLevel;
00394           }
00395           break;
00396         case '\\' : // quoted character
00397           ++index; // skip the '\'
00398           if (( index + 1 )> strlen ) {
00399             return UnexpectedEnd;
00400           }
00401           break;
00402         }
00403         break;
00404     }
00405 
00406     case InAngleAddress : {
00407       switch ( aStr[index] ) {
00408             case ',' :
00409             case ';' :
00410           if ( !inQuotedString ) {
00411             return UnexpectedComma;
00412           }
00413           break;
00414         case '"' : inQuotedString = !inQuotedString;
00415             break;
00416         case '@' :
00417           if ( inQuotedString ) {
00418             --atCount;
00419             if ( atCount == 1 ) {
00420               tooManyAtsFlag = false;
00421             }
00422           }
00423           break;
00424         case '>' :
00425           if ( !inQuotedString ) {
00426             context = TopLevel;
00427             break;
00428           }
00429           break;
00430         case '\\' : // quoted character
00431           ++index; // skip the '\'
00432           if (( index + 1 )> strlen ) {
00433             return UnexpectedEnd;
00434           }
00435           break;
00436         }
00437         break;
00438       }
00439     }
00440   }
00441 
00442   if ( atCount == 0 && !inQuotedString )
00443     return TooFewAts;
00444 
00445   if ( inQuotedString )
00446     return UnbalancedQuote;
00447 
00448   if ( context == InComment )
00449     return UnbalancedParens;
00450 
00451   if ( context == InAngleAddress )
00452     return UnclosedAngleAddr;
00453 
00454   if ( tooManyAtsFlag ) {
00455     return TooManyAts;
00456   }
00457   return AddressOk;
00458 }
00459 
00460 //-----------------------------------------------------------------------------
00461 TQString KPIM::emailParseResultToString( EmailParseResult errorCode )
00462 {
00463   switch ( errorCode ) {
00464     case TooManyAts :
00465       return i18n("The email address you entered is not valid because it "
00466                 "contains more than one @. "
00467                 "You will not create valid messages if you do not "
00468                 "change your address.");
00469     case TooFewAts :
00470       return i18n("The email address you entered is not valid because it "
00471                 "does not contain a @."
00472                 "You will not create valid messages if you do not "
00473                 "change your address.");
00474     case AddressEmpty :
00475       return i18n("You have to enter something in the email address field.");
00476     case MissingLocalPart :
00477       return i18n("The email address you entered is not valid because it "
00478                 "does not contain a local part.");
00479     case MissingDomainPart :
00480       return i18n("The email address you entered is not valid because it "
00481                 "does not contain a domain part.");
00482     case UnbalancedParens :
00483       return i18n("The email address you entered is not valid because it "
00484                 "contains unclosed comments/brackets.");
00485     case AddressOk :
00486       return i18n("The email address you entered is valid.");
00487     case UnclosedAngleAddr :
00488       return i18n("The email address you entered is not valid because it "
00489                 "contains an unclosed anglebracket.");
00490     case UnopenedAngleAddr :
00491       return i18n("The email address you entered is not valid because it "
00492                 "contains an unopened anglebracket.");
00493     case UnexpectedComma :
00494       return i18n("The email address you have entered is not valid because it "
00495                 "contains an unexpected comma.");
00496     case UnexpectedEnd :
00497       return i18n("The email address you entered is not valid because it ended "
00498                 "unexpectedly, this probably means you have used an escaping type "
00499                 "character like an \\  as the last character in your email "
00500                 "address.");
00501     case UnbalancedQuote :
00502       return i18n("The email address you entered is not valid because it "
00503                   "contains quoted text which does not end.");
00504     case NoAddressSpec :
00505       return i18n("The email address you entered is not valid because it "
00506                   "does not seem to contain an actual email address, i.e. "
00507                   "something of the form joe@kde.org.");
00508     case DisallowedChar :
00509       return i18n("The email address you entered is not valid because it "
00510                   "contains an illegal character.");
00511     case InvalidDisplayName :
00512       return i18n("The email address you have entered is not valid because it "
00513                   "contains an invalid displayname.");
00514   }
00515   return i18n("Unknown problem with email address");
00516 }
00517 
00518 //-----------------------------------------------------------------------------
00519 bool KPIM::isValidSimpleEmailAddress( const TQString& aStr )
00520 {
00521   // If we are passed an empty string bail right away no need to process further
00522   // and waste resources
00523   if ( aStr.isEmpty() ) {
00524     return false;
00525   }
00526 
00527   int atChar = aStr.findRev( '@' );
00528   TQString domainPart = aStr.mid( atChar + 1);
00529   TQString localPart = aStr.left( atChar );
00530   bool tooManyAtsFlag = false;
00531   bool inQuotedString = false;
00532   int atCount = localPart.contains( '@' );
00533 
00534   unsigned int strlen = localPart.length();
00535   for ( unsigned int index=0; index < strlen; index++ ) {
00536     switch( localPart[ index ].latin1() ) {
00537       case '"' : inQuotedString = !inQuotedString;
00538         break;
00539       case '@' :
00540         if ( inQuotedString ) {
00541           --atCount;
00542           if ( atCount == 0 ) {
00543             tooManyAtsFlag = false;
00544           }
00545         }
00546         break;
00547       }
00548   }
00549 
00550   TQString addrRx = "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
00551   if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) {
00552     addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@";
00553   }
00554   if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) {
00555     addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]";
00556   } else {
00557     addrRx += "[\\w-]+(\\.[\\w-]+)*";
00558   }
00559   TQRegExp rx( addrRx );
00560   return  rx.exactMatch( aStr ) && !tooManyAtsFlag;
00561 }
00562 
00563 //-----------------------------------------------------------------------------
00564 TQString KPIM::simpleEmailAddressErrorMsg()
00565 {
00566       return i18n("The email address you entered is not valid because it "
00567                   "does not seem to contain an actual email address, i.e. "
00568                   "something of the form joe@kde.org.");
00569 }
00570 //-----------------------------------------------------------------------------
00571 TQCString KPIM::getEmailAddress( const TQCString & address )
00572 {
00573   TQCString dummy1, dummy2, addrSpec;
00574   KPIM::EmailParseResult result =
00575     splitAddressInternal( address, dummy1, addrSpec, dummy2,
00576                           false /* don't allow multiple addresses */ );
00577   if ( result != AddressOk ) {
00578     addrSpec = TQCString();
00579     kdDebug() // << k_funcinfo << "\n"
00580               << "Input: aStr\nError:"
00581               << emailParseResultToString( result ) << endl;
00582   }
00583 
00584   return addrSpec;
00585 }
00586 
00587 
00588 //-----------------------------------------------------------------------------
00589 TQString KPIM::getEmailAddress( const TQString & address )
00590 {
00591   return TQString::fromUtf8( getEmailAddress( address.utf8() ) );
00592 }
00593 
00594 
00595 //-----------------------------------------------------------------------------
00596 TQCString KPIM::getFirstEmailAddress( const TQCString & addresses )
00597 {
00598   TQCString dummy1, dummy2, addrSpec;
00599   KPIM::EmailParseResult result =
00600     splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
00601                           true /* allow multiple addresses */ );
00602   if ( result != AddressOk ) {
00603     addrSpec = TQCString();
00604     kdDebug() // << k_funcinfo << "\n"
00605               << "Input: aStr\nError:"
00606               << emailParseResultToString( result ) << endl;
00607   }
00608 
00609   return addrSpec;
00610 }
00611 
00612 
00613 //-----------------------------------------------------------------------------
00614 TQString KPIM::getFirstEmailAddress( const TQString & addresses )
00615 {
00616   return TQString::fromUtf8( getFirstEmailAddress( addresses.utf8() ) );
00617 }
00618 
00619 
00620 //-----------------------------------------------------------------------------
00621 bool KPIM::getNameAndMail(const TQString& aStr, TQString& name, TQString& mail)
00622 {
00623   name = TQString();
00624   mail = TQString();
00625 
00626   const int len=aStr.length();
00627   const char cQuotes = '"';
00628 
00629   bool bInComment = false;
00630   bool bInQuotesOutsideOfEmail = false;
00631   int i=0, iAd=0, iMailStart=0, iMailEnd=0;
00632   TQChar c;
00633   unsigned int commentstack = 0;
00634 
00635   // Find the '@' of the email address
00636   // skipping all '@' inside "(...)" comments:
00637   while( i < len ){
00638     c = aStr[i];
00639     if( '(' == c ) commentstack++;
00640     if( ')' == c ) commentstack--;
00641     bInComment = commentstack != 0;
00642     if( '"' == c && !bInComment ) 
00643         bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
00644 
00645     if( !bInComment && !bInQuotesOutsideOfEmail ){
00646       if( '@' == c ){
00647         iAd = i;
00648         break; // found it
00649       }
00650     }
00651     ++i;
00652   }
00653 
00654   if ( !iAd ) {
00655     // We suppose the user is typing the string manually and just
00656     // has not finished typing the mail address part.
00657     // So we take everything that's left of the '<' as name and the rest as mail
00658     for( i = 0; len > i; ++i ) {
00659       c = aStr[i];
00660       if( '<' != c )
00661         name.append( c );
00662       else
00663         break;
00664     }
00665     mail = aStr.mid( i+1 );
00666     if ( mail.endsWith( ">" ) )
00667       mail.truncate( mail.length() - 1 );
00668 
00669   } else {
00670     // Loop backwards until we find the start of the string
00671     // or a ',' that is outside of a comment
00672     //          and outside of quoted text before the leading '<'.
00673     bInComment = false;
00674     bInQuotesOutsideOfEmail = false;
00675     for( i = iAd-1; 0 <= i; --i ) {
00676       c = aStr[i];
00677       if( bInComment ) {
00678         if( '(' == c ) {
00679           if( !name.isEmpty() )
00680             name.prepend( ' ' );
00681           bInComment = false;
00682         } else {
00683           name.prepend( c ); // all comment stuff is part of the name
00684         }
00685       }else if( bInQuotesOutsideOfEmail ){
00686         if( cQuotes == c )
00687           bInQuotesOutsideOfEmail = false;
00688         else if ( c != '\\' )
00689           name.prepend( c );
00690       }else{
00691         // found the start of this addressee ?
00692         if( ',' == c )
00693           break;
00694         // stuff is before the leading '<' ?
00695         if( iMailStart ){
00696           if( cQuotes == c )
00697             bInQuotesOutsideOfEmail = true; // end of quoted text found
00698           else {
00699             name.prepend( c );
00700           }
00701         }else{
00702           switch( c ){
00703             case '<':
00704               iMailStart = i;
00705               break;
00706             case ')':
00707               if( !name.isEmpty() )
00708                 name.prepend( ' ' );
00709               bInComment = true;
00710               break;
00711             default:
00712               if( ' ' != c )
00713                 mail.prepend( c );
00714           }
00715         }
00716       }
00717     }
00718 
00719     name = name.simplifyWhiteSpace();
00720     mail = mail.simplifyWhiteSpace();
00721 
00722     if( mail.isEmpty() )
00723       return false;
00724 
00725     mail.append('@');
00726 
00727     // Loop forward until we find the end of the string
00728     // or a ',' that is outside of a comment
00729     //          and outside of quoted text behind the trailing '>'.
00730     bInComment = false;
00731     bInQuotesOutsideOfEmail = false;
00732     int parenthesesNesting = 0;
00733     for( i = iAd+1; len > i; ++i ) {
00734       c = aStr[i];
00735       if( bInComment ){
00736         if( ')' == c ){
00737           if ( --parenthesesNesting == 0 ) {
00738             bInComment = false;
00739             if( !name.isEmpty() )
00740               name.append( ' ' );
00741           } else {
00742             // nested ")", add it
00743             name.append( ')' ); // name can't be empty here
00744           }
00745         } else {
00746           if( '(' == c ) {
00747             // nested "("
00748             ++parenthesesNesting;
00749           }
00750           name.append( c ); // all comment stuff is part of the name
00751         }
00752       }else if( bInQuotesOutsideOfEmail ){
00753         if( cQuotes == c )
00754           bInQuotesOutsideOfEmail = false;
00755         else  if ( c != '\\' )
00756           name.append( c );
00757       }else{
00758         // found the end of this addressee ?
00759         if( ',' == c )
00760           break;
00761         // stuff is behind the trailing '>' ?
00762         if( iMailEnd ){
00763           if( cQuotes == c )
00764             bInQuotesOutsideOfEmail = true; // start of quoted text found
00765           else
00766             name.append( c );
00767         }else{
00768           switch( c ){
00769             case '>':
00770               iMailEnd = i;
00771               break;
00772             case '(':
00773               if( !name.isEmpty() )
00774                 name.append( ' ' );
00775               if ( ++parenthesesNesting > 0 )
00776                 bInComment = true;
00777               break;
00778             default:
00779               if( ' ' != c )
00780                 mail.append( c );
00781           }
00782         }
00783       }
00784     }
00785   }
00786 
00787   name = name.simplifyWhiteSpace();
00788   mail = mail.simplifyWhiteSpace();
00789 
00790   return ! (name.isEmpty() || mail.isEmpty());
00791 }
00792 
00793 
00794 //-----------------------------------------------------------------------------
00795 bool KPIM::compareEmail( const TQString& email1, const TQString& email2,
00796                          bool matchName )
00797 {
00798   TQString e1Name, e1Email, e2Name, e2Email;
00799 
00800   getNameAndMail( email1, e1Name, e1Email );
00801   getNameAndMail( email2, e2Name, e2Email );
00802 
00803   return e1Email == e2Email &&
00804     ( !matchName || ( e1Name == e2Name ) );
00805 }
00806 
00807 
00808 //-----------------------------------------------------------------------------
00809 TQString KPIM::normalizedAddress( const TQString & displayName,
00810                                  const TQString & addrSpec,
00811                                  const TQString & comment )
00812 {
00813   TQString realDisplayName = displayName;
00814   realDisplayName.remove( TQChar( 0x202D ) );
00815   realDisplayName.remove( TQChar( 0x202E ) );
00816   realDisplayName.remove( TQChar( 0x202A ) );
00817   realDisplayName.remove( TQChar( 0x202B ) );
00818 
00819   if ( realDisplayName.isEmpty() && comment.isEmpty() )
00820     return addrSpec;
00821   else if ( comment.isEmpty() )
00822     return quoteNameIfNecessary( realDisplayName ) + " <" + addrSpec + ">";
00823   else if ( realDisplayName.isEmpty() ) {
00824     TQString commentStr = comment;
00825     return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + ">";
00826   }
00827   else
00828     return realDisplayName + " (" + comment + ") <" + addrSpec + ">";
00829 }
00830 
00831 
00832 //-----------------------------------------------------------------------------
00833 TQString KPIM::decodeIDN( const TQString & addrSpec )
00834 {
00835   const int atPos = addrSpec.findRev( '@' );
00836   if ( atPos == -1 )
00837     return addrSpec;
00838 
00839   TQString idn = KIDNA::toUnicode( addrSpec.mid( atPos + 1 ) );
00840   if ( idn.isEmpty() )
00841     return TQString();
00842 
00843   return addrSpec.left( atPos + 1 ) + idn;
00844 }
00845 
00846 
00847 //-----------------------------------------------------------------------------
00848 TQString KPIM::encodeIDN( const TQString & addrSpec )
00849 {
00850   const int atPos = addrSpec.findRev( '@' );
00851   if ( atPos == -1 )
00852     return addrSpec;
00853 
00854   TQString idn = KIDNA::toAscii( addrSpec.mid( atPos + 1 ) );
00855   if ( idn.isEmpty() )
00856     return addrSpec;
00857 
00858   return addrSpec.left( atPos + 1 ) + idn;
00859 }
00860 
00861 
00862 //-----------------------------------------------------------------------------
00863 TQString KPIM::normalizeAddressesAndDecodeIDNs( const TQString & str )
00864 {
00865 //  kdDebug() << "KPIM::normalizeAddressesAndDecodeIDNs( \""
00866 //                << str << "\" )" << endl;
00867   if( str.isEmpty() )
00868     return str;
00869 
00870   const TQStringList addressList = KPIM::splitEmailAddrList( str );
00871   TQStringList normalizedAddressList;
00872 
00873   TQCString displayName, addrSpec, comment;
00874 
00875   for( TQStringList::ConstIterator it = addressList.begin();
00876        ( it != addressList.end() );
00877        ++it ) {
00878     if( !(*it).isEmpty() ) {
00879       if ( KPIM::splitAddress( (*it).utf8(), displayName, addrSpec, comment )
00880            == AddressOk ) {
00881 
00882         displayName = KMime::decodeRFC2047String(displayName).utf8();
00883         comment = KMime::decodeRFC2047String(comment).utf8();
00884 
00885         normalizedAddressList <<
00886           normalizedAddress( TQString::fromUtf8( displayName ),
00887                              decodeIDN( TQString::fromUtf8( addrSpec ) ),
00888                              TQString::fromUtf8( comment ) );
00889       }
00890       else {
00891         kdDebug() << "splitting address failed: " << *it << endl;
00892       }
00893     }
00894   }
00895 /*
00896   kdDebug() << "normalizedAddressList: \""
00897                 << normalizedAddressList.join( ", " )
00898                 << "\"" << endl;
00899 */
00900   return normalizedAddressList.join( ", " );
00901 }
00902 
00903 //-----------------------------------------------------------------------------
00904 TQString KPIM::normalizeAddressesAndEncodeIDNs( const TQString & str )
00905 {
00906   //kdDebug() << "KPIM::normalizeAddressesAndEncodeIDNs( \""
00907   //              << str << "\" )" << endl;
00908   if( str.isEmpty() )
00909     return str;
00910 
00911   const TQStringList addressList = KPIM::splitEmailAddrList( str );
00912   TQStringList normalizedAddressList;
00913 
00914   TQCString displayName, addrSpec, comment;
00915 
00916   for( TQStringList::ConstIterator it = addressList.begin();
00917        ( it != addressList.end() );
00918        ++it ) {
00919     if( !(*it).isEmpty() ) {
00920       if ( KPIM::splitAddress( (*it).utf8(), displayName, addrSpec, comment )
00921            == AddressOk ) {
00922 
00923         normalizedAddressList <<
00924           normalizedAddress( TQString::fromUtf8( displayName ),
00925                              encodeIDN( TQString::fromUtf8( addrSpec ) ),
00926                              TQString::fromUtf8( comment ) );
00927       }
00928       else {
00929         kdDebug() << "splitting address failed: " << *it << endl;
00930       }
00931     }
00932   }
00933 
00934   /*
00935   kdDebug() << "normalizedAddressList: \""
00936                 << normalizedAddressList.join( ", " )
00937                 << "\"" << endl;
00938   */
00939   return normalizedAddressList.join( ", " );
00940 }
00941 
00942 
00943 //-----------------------------------------------------------------------------
00944 // Escapes unescaped doublequotes in str.
00945 static TQString escapeQuotes( const TQString & str )
00946 {
00947   if ( str.isEmpty() )
00948     return TQString();
00949 
00950   TQString escaped;
00951   // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
00952   escaped.reserve( 2*str.length() );
00953   unsigned int len = 0;
00954   for ( unsigned int i = 0; i < str.length(); ++i, ++len ) {
00955     if ( str[i] == '"' ) { // unescaped doublequote
00956       escaped[len] = '\\';
00957       ++len;
00958     }
00959     else if ( str[i] == '\\' ) { // escaped character
00960       escaped[len] = '\\';
00961       ++len;
00962       ++i;
00963       if ( i >= str.length() ) // handle trailing '\' gracefully
00964         break;
00965     }
00966     escaped[len] = str[i];
00967   }
00968   escaped.truncate( len );
00969   return escaped;
00970 }
00971 
00972 //-----------------------------------------------------------------------------
00973 TQString KPIM::quoteNameIfNecessary( const TQString &str )
00974 {
00975   TQString quoted = str;
00976 
00977   TQRegExp needQuotes(  "[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
00978   // avoid double quoting
00979   if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) {
00980     quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\"";
00981   }
00982   else if ( quoted.find( needQuotes ) != -1 ) {
00983     quoted = "\"" + escapeQuotes( quoted ) + "\"";
00984   }
00985 
00986   return quoted;
00987 }
00988