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