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