libemailfunctions

email.cpp

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