libemailfunctions

email.cpp

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