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