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