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