• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

kpimutils

email.cpp

Go to the documentation of this file.
00001 /*
00002   This file is part of the kpimutils library.
00003   Copyright (c) 2004 Matt Douhan <matt@fruitsalad.org>
00004 
00005   This library is free software; you can redistribute it and/or
00006   modify it under the terms of the GNU Library General Public
00007   License as published by the Free Software Foundation; either
00008   version 2 of the License, or (at your option) any later version.
00009 
00010   This library is distributed in the hope that it will be useful,
00011   but WITHOUT ANY WARRANTY; without even the implied warranty of
00012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013   Library General Public License for more details.
00014 
00015   You should have received a copy of the GNU Library General Public License
00016   along with this library; see the file COPYING.LIB.  If not, write to
00017   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018   Boston, MA 02110-1301, USA.
00019 */
00028 #include "email.h"
00029 
00030 #include <kdebug.h>
00031 #include <klocale.h>
00032 #include <kurl.h>
00033 #include <kmime_util.h>
00034 
00035 #include <QtCore/QRegExp>
00036 #include <QtCore/QByteArray>
00037 
00038 using namespace KPIMUtils;
00039 
00040 //-----------------------------------------------------------------------------
00041 QStringList KPIMUtils::splitAddressList( const QString &aStr )
00042 {
00043   // Features:
00044   // - always ignores quoted characters
00045   // - ignores everything (including parentheses and commas)
00046   //   inside quoted strings
00047   // - supports nested comments
00048   // - ignores everything (including double quotes and commas)
00049   //   inside comments
00050 
00051   QStringList list;
00052 
00053   if ( aStr.isEmpty() ) {
00054     return list;
00055   }
00056 
00057   QString addr;
00058   uint addrstart = 0;
00059   int commentlevel = 0;
00060   bool insidequote = false;
00061 
00062   for ( int index=0; index<aStr.length(); index++ ) {
00063     // the following conversion to latin1 is o.k. because
00064     // we can safely ignore all non-latin1 characters
00065     switch ( aStr[index].toLatin1() ) {
00066     case '"' : // start or end of quoted string
00067       if ( commentlevel == 0 ) {
00068         insidequote = !insidequote;
00069       }
00070       break;
00071     case '(' : // start of comment
00072       if ( !insidequote ) {
00073         commentlevel++;
00074       }
00075       break;
00076     case ')' : // end of comment
00077       if ( !insidequote ) {
00078         if ( commentlevel > 0 ) {
00079           commentlevel--;
00080         } else {
00081           kDebug() << "Error in address splitting: Unmatched ')'";
00082           return list;
00083         }
00084       }
00085       break;
00086     case '\\' : // quoted character
00087       index++; // ignore the quoted character
00088       break;
00089     case ',' :
00090       if ( !insidequote && ( commentlevel == 0 ) ) {
00091         addr = aStr.mid( addrstart, index - addrstart );
00092         if ( !addr.isEmpty() ) {
00093           list += addr.simplified();
00094         }
00095         addrstart = index + 1;
00096       }
00097       break;
00098     }
00099   }
00100   // append the last address to the list
00101   if ( !insidequote && ( commentlevel == 0 ) ) {
00102     addr = aStr.mid( addrstart, aStr.length() - addrstart );
00103     if ( !addr.isEmpty() ) {
00104       list += addr.simplified();
00105     }
00106   } else {
00107     kDebug() << "Error in address splitting: Unexpected end of address list";
00108   }
00109 
00110   return list;
00111 }
00112 
00113 //-----------------------------------------------------------------------------
00114 // Used by KPIMUtils::splitAddress(...) and KPIMUtils::firstEmailAddress(...).
00115 KPIMUtils::EmailParseResult splitAddressInternal( const QByteArray  address,
00116                                                   QByteArray &displayName,
00117                                                   QByteArray &addrSpec,
00118                                                   QByteArray &comment,
00119                                                   bool allowMultipleAddresses )
00120 {
00121   //  kDebug() << "address";
00122 
00123   displayName = "";
00124   addrSpec = "";
00125   comment = "";
00126 
00127   if ( address.isEmpty() ) {
00128     return AddressEmpty;
00129   }
00130 
00131   // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
00132   // The purpose is to extract a displayable string from the mailboxes.
00133   // Comments in the addr-spec are not handled. No error checking is done.
00134 
00135   enum {
00136     TopLevel,
00137     InComment,
00138     InAngleAddress
00139   } context = TopLevel;
00140   bool inQuotedString = false;
00141   int commentLevel = 0;
00142   bool stop = false;
00143 
00144   for ( const char *p = address.data(); *p && !stop; ++p ) {
00145     switch ( context ) {
00146     case TopLevel :
00147       {
00148       switch ( *p ) {
00149       case '"' :
00150         inQuotedString = !inQuotedString;
00151         displayName += *p;
00152         break;
00153       case '(' :
00154         if ( !inQuotedString ) {
00155           context = InComment;
00156           commentLevel = 1;
00157         } else {
00158           displayName += *p;
00159         }
00160         break;
00161       case '<' :
00162         if ( !inQuotedString ) {
00163           context = InAngleAddress;
00164         } else {
00165           displayName += *p;
00166         }
00167         break;
00168       case '\\' : // quoted character
00169         displayName += *p;
00170         ++p; // skip the '\'
00171         if ( *p ) {
00172           displayName += *p;
00173         } else {
00174           return UnexpectedEnd;
00175         }
00176         break;
00177       case ',' :
00178         if ( !inQuotedString ) {
00179           if ( allowMultipleAddresses ) {
00180             stop = true;
00181           } else {
00182             return UnexpectedComma;
00183           }
00184         } else {
00185           displayName += *p;
00186         }
00187         break;
00188       default :
00189         displayName += *p;
00190       }
00191       break;
00192       }
00193     case InComment :
00194       {
00195       switch ( *p ) {
00196       case '(' :
00197         ++commentLevel;
00198         comment += *p;
00199         break;
00200       case ')' :
00201         --commentLevel;
00202         if ( commentLevel == 0 ) {
00203           context = TopLevel;
00204           comment += ' '; // separate the text of several comments
00205         } else {
00206           comment += *p;
00207         }
00208         break;
00209       case '\\' : // quoted character
00210         comment += *p;
00211         ++p; // skip the '\'
00212         if ( *p ) {
00213           comment += *p;
00214         } else {
00215           return UnexpectedEnd;
00216         }
00217         break;
00218       default :
00219         comment += *p;
00220       }
00221       break;
00222       }
00223     case InAngleAddress :
00224       {
00225         switch ( *p ) {
00226       case '"' :
00227         inQuotedString = !inQuotedString;
00228         addrSpec += *p;
00229         break;
00230       case '>' :
00231         if ( !inQuotedString ) {
00232           context = TopLevel;
00233         } else {
00234           addrSpec += *p;
00235         }
00236         break;
00237       case '\\' : // quoted character
00238         addrSpec += *p;
00239         ++p; // skip the '\'
00240         if ( *p ) {
00241           addrSpec += *p;
00242         } else {
00243           return UnexpectedEnd;
00244         }
00245         break;
00246       default :
00247         addrSpec += *p;
00248       }
00249       break;
00250     }
00251     } // switch ( context )
00252   }
00253   // check for errors
00254   if ( inQuotedString ) {
00255     return UnbalancedQuote;
00256   }
00257   if ( context == InComment ) {
00258     return UnbalancedParens;
00259   }
00260   if ( context == InAngleAddress ) {
00261     return UnclosedAngleAddr;
00262   }
00263 
00264   displayName = displayName.trimmed();
00265   comment = comment.trimmed();
00266   addrSpec = addrSpec.trimmed();
00267 
00268   if ( addrSpec.isEmpty() ) {
00269     if ( displayName.isEmpty() ) {
00270       return NoAddressSpec;
00271     } else {
00272       addrSpec = displayName;
00273       displayName.truncate( 0 );
00274     }
00275   }
00276   /*
00277     kDebug() << "display-name : \"" << displayName << "\"";
00278     kDebug() << "comment      : \"" << comment << "\"";
00279     kDebug() << "addr-spec    : \"" << addrSpec << "\"";
00280   */
00281   return AddressOk;
00282 }
00283 
00284 //-----------------------------------------------------------------------------
00285 EmailParseResult KPIMUtils::splitAddress( const QByteArray &address,
00286                                           QByteArray &displayName,
00287                                           QByteArray &addrSpec,
00288                                           QByteArray &comment )
00289 {
00290   return splitAddressInternal( address, displayName, addrSpec, comment,
00291                                false/* don't allow multiple addresses */ );
00292 }
00293 
00294 //-----------------------------------------------------------------------------
00295 EmailParseResult KPIMUtils::splitAddress( const QString &address,
00296                                           QString &displayName,
00297                                           QString &addrSpec,
00298                                           QString &comment )
00299 {
00300   QByteArray d, a, c;
00301   EmailParseResult result = splitAddress( address.toUtf8(), d, a, c );
00302 
00303   if ( result == AddressOk ) {
00304     displayName = QString::fromUtf8( d );
00305     addrSpec = QString::fromUtf8( a );
00306     comment = QString::fromUtf8( c );
00307   }
00308   return result;
00309 }
00310 
00311 //-----------------------------------------------------------------------------
00312 EmailParseResult KPIMUtils::isValidAddress( const QString &aStr )
00313 {
00314   // If we are passed an empty string bail right away no need to process
00315   // further and waste resources
00316   if ( aStr.isEmpty() ) {
00317     return AddressEmpty;
00318   }
00319 
00320   // count how many @'s are in the string that is passed to us
00321   // if 0 or > 1 take action
00322   // at this point to many @'s cannot bail out right away since
00323   // @ is allowed in qoutes, so we use a bool to keep track
00324   // and then make a judgment further down in the parser
00325   // FIXME count only @ not in double quotes
00326 
00327   bool tooManyAtsFlag = false;
00328 
00329   int atCount = aStr.count( '@' );
00330   if ( atCount > 1 ) {
00331     tooManyAtsFlag = true;
00332   } else if ( atCount == 0 ) {
00333     return TooFewAts;
00334   }
00335 
00336   // The main parser, try and catch all weird and wonderful
00337   // mistakes users and/or machines can create
00338 
00339   enum {
00340     TopLevel,
00341     InComment,
00342     InAngleAddress
00343   } context = TopLevel;
00344   bool inQuotedString = false;
00345   int commentLevel = 0;
00346 
00347   unsigned int strlen = aStr.length();
00348 
00349   for ( unsigned int index=0; index < strlen; index++ ) {
00350     switch ( context ) {
00351     case TopLevel :
00352       {
00353         switch ( aStr[index].toLatin1() ) {
00354         case '"' :
00355           inQuotedString = !inQuotedString;
00356           break;
00357         case '(' :
00358           if ( !inQuotedString ) {
00359             context = InComment;
00360             commentLevel = 1;
00361           }
00362           break;
00363         case '[' :
00364           if ( !inQuotedString ) {
00365             return InvalidDisplayName;
00366           }
00367           break;
00368         case ']' :
00369           if ( !inQuotedString ) {
00370             return InvalidDisplayName;
00371           }
00372           break;
00373         case ':' :
00374           if ( !inQuotedString ) {
00375             return DisallowedChar;
00376           }
00377           break;
00378         case '<' :
00379           if ( !inQuotedString ) {
00380             context = InAngleAddress;
00381           }
00382           break;
00383         case '\\' : // quoted character
00384           ++index; // skip the '\'
00385           if ( ( index + 1 ) > strlen ) {
00386             return UnexpectedEnd;
00387           }
00388           break;
00389         case ',' :
00390           if ( !inQuotedString ) {
00391             return UnexpectedComma;
00392           }
00393           break;
00394         case ')' :
00395           if ( !inQuotedString ) {
00396             return UnbalancedParens;
00397           }
00398           break;
00399         case '>' :
00400           if ( !inQuotedString ) {
00401             return UnopenedAngleAddr;
00402           }
00403           break;
00404         case '@' :
00405           if ( !inQuotedString ) {
00406             if ( index == 0 ) {  // Missing local part
00407               return MissingLocalPart;
00408             } else if ( index == strlen-1 ) {
00409               return MissingDomainPart;
00410               break;
00411             }
00412           } else if ( inQuotedString ) {
00413             --atCount;
00414             if ( atCount == 1 ) {
00415               tooManyAtsFlag = false;
00416             }
00417           }
00418           break;
00419         }
00420         break;
00421       }
00422     case InComment :
00423       {
00424         switch ( aStr[index].toLatin1() ) {
00425         case '(' :
00426           ++commentLevel;
00427           break;
00428         case ')' :
00429           --commentLevel;
00430           if ( commentLevel == 0 ) {
00431             context = TopLevel;
00432           }
00433           break;
00434         case '\\' : // quoted character
00435           ++index; // skip the '\'
00436           if ( ( index + 1 ) > strlen ) {
00437             return UnexpectedEnd;
00438           }
00439           break;
00440         }
00441         break;
00442       }
00443 
00444     case InAngleAddress :
00445       {
00446         switch ( aStr[index].toLatin1() ) {
00447         case ',' :
00448           if ( !inQuotedString ) {
00449             return UnexpectedComma;
00450           }
00451           break;
00452         case '"' :
00453           inQuotedString = !inQuotedString;
00454           break;
00455         case '@' :
00456           if ( inQuotedString ) {
00457             --atCount;
00458             if ( atCount == 1 ) {
00459               tooManyAtsFlag = false;
00460             }
00461           }
00462           break;
00463         case '>' :
00464           if ( !inQuotedString ) {
00465             context = TopLevel;
00466             break;
00467           }
00468           break;
00469         case '\\' : // quoted character
00470           ++index; // skip the '\'
00471           if ( ( index + 1 ) > strlen ) {
00472             return UnexpectedEnd;
00473           }
00474           break;
00475         }
00476         break;
00477       }
00478     }
00479   }
00480 
00481   if ( atCount == 0 && !inQuotedString ) {
00482     return TooFewAts;
00483   }
00484 
00485   if ( inQuotedString ) {
00486     return UnbalancedQuote;
00487   }
00488 
00489   if ( context == InComment ) {
00490     return UnbalancedParens;
00491   }
00492 
00493   if ( context == InAngleAddress ) {
00494     return UnclosedAngleAddr;
00495   }
00496 
00497   if ( tooManyAtsFlag ) {
00498     return TooManyAts;
00499   }
00500 
00501   return AddressOk;
00502 }
00503 
00504 //-----------------------------------------------------------------------------
00505 KPIMUtils::EmailParseResult KPIMUtils::isValidAddressList( const QString &aStr,
00506                                                            QString &badAddr )
00507 {
00508   if ( aStr.isEmpty() ) {
00509     return AddressEmpty;
00510   }
00511 
00512   QStringList list = splitAddressList( aStr );
00513 
00514   QStringList::const_iterator it = list.begin();
00515   EmailParseResult errorCode = AddressOk;
00516   for ( it = list.begin(); it != list.end(); ++it ) {
00517     errorCode = isValidAddress( *it );
00518     if ( errorCode != AddressOk ) {
00519       badAddr = ( *it );
00520       break;
00521     }
00522   }
00523   return errorCode;
00524 }
00525 
00526 //-----------------------------------------------------------------------------
00527 QString KPIMUtils::emailParseResultToString( EmailParseResult errorCode )
00528 {
00529   switch ( errorCode ) {
00530   case TooManyAts :
00531     return i18n( "The email address you entered is not valid because it "
00532                  "contains more than one @. "
00533                  "You will not create valid messages if you do not "
00534                  "change your address." );
00535   case TooFewAts :
00536     return i18n( "The email address you entered is not valid because it "
00537                  "does not contain a @."
00538                  "You will not create valid messages if you do not "
00539                  "change your address." );
00540   case AddressEmpty :
00541     return i18n( "You have to enter something in the email address field." );
00542   case MissingLocalPart :
00543     return i18n( "The email address you entered is not valid because it "
00544                  "does not contain a local part." );
00545   case MissingDomainPart :
00546     return i18n( "The email address you entered is not valid because it "
00547                  "does not contain a domain part." );
00548   case UnbalancedParens :
00549     return i18n( "The email address you entered is not valid because it "
00550                  "contains unclosed comments/brackets." );
00551   case AddressOk :
00552     return i18n( "The email address you entered is valid." );
00553   case UnclosedAngleAddr :
00554     return i18n( "The email address you entered is not valid because it "
00555                  "contains an unclosed anglebracket." );
00556   case UnopenedAngleAddr :
00557     return i18n( "The email address you entered is not valid because it "
00558                  "contains an unopened anglebracket." );
00559   case UnexpectedComma :
00560     return i18n( "The email address you have entered is not valid because it "
00561                  "contains an unexpected comma." );
00562   case UnexpectedEnd :
00563     return i18n( "The email address you entered is not valid because it ended "
00564                  "unexpectedly, this probably means you have used an escaping "
00565                  "type character like an \\  as the last character in your "
00566                  "email address." );
00567   case UnbalancedQuote :
00568     return i18n( "The email address you entered is not valid because it "
00569                  "contains quoted text which does not end." );
00570   case NoAddressSpec :
00571     return i18n( "The email address you entered is not valid because it "
00572                  "does not seem to contain an actual email address, i.e. "
00573                  "something of the form joe@example.org." );
00574   case DisallowedChar :
00575     return i18n( "The email address you entered is not valid because it "
00576                  "contains an illegal character." );
00577   case InvalidDisplayName :
00578     return i18n( "The email address you have entered is not valid because it "
00579                  "contains an invalid displayname." );
00580   }
00581   return i18n( "Unknown problem with email address" );
00582 }
00583 
00584 //-----------------------------------------------------------------------------
00585 bool KPIMUtils::isValidSimpleAddress( const QString &aStr )
00586 {
00587   // If we are passed an empty string bail right away no need to process further
00588   // and waste resources
00589   if ( aStr.isEmpty() ) {
00590     return false;
00591   }
00592 
00593   int atChar = aStr.lastIndexOf( '@' );
00594   QString domainPart = aStr.mid( atChar + 1 );
00595   QString localPart = aStr.left( atChar );
00596   bool tooManyAtsFlag = false;
00597   bool inQuotedString = false;
00598   int atCount = localPart.count( '@' );
00599 
00600   unsigned int strlen = localPart.length();
00601   for ( unsigned int index=0; index < strlen; index++ ) {
00602     switch( localPart[ index ].toLatin1() ) {
00603     case '"' :
00604       inQuotedString = !inQuotedString;
00605       break;
00606     case '@' :
00607       if ( inQuotedString ) {
00608         --atCount;
00609         if ( atCount == 0 ) {
00610           tooManyAtsFlag = false;
00611         }
00612       }
00613       break;
00614     }
00615   }
00616 
00617   QString addrRx =
00618     "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
00619 
00620   if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) {
00621     addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@";
00622   }
00623   if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) {
00624     addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]";
00625   } else {
00626     addrRx += "[\\w-]+(\\.[\\w-]+)*";
00627   }
00628   QRegExp rx( addrRx );
00629   return  rx.exactMatch( aStr ) && !tooManyAtsFlag;
00630 }
00631 
00632 //-----------------------------------------------------------------------------
00633 QString KPIMUtils::simpleEmailAddressErrorMsg()
00634 {
00635   return i18n( "The email address you entered is not valid because it "
00636                "does not seem to contain an actual email address, i.e. "
00637                "something of the form joe@example.org." );
00638 }
00639 
00640 //-----------------------------------------------------------------------------
00641 QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address )
00642 {
00643   QByteArray dummy1, dummy2, addrSpec;
00644   EmailParseResult result =
00645     splitAddressInternal( address, dummy1, addrSpec, dummy2,
00646                           false/* don't allow multiple addresses */ );
00647   if ( result != AddressOk ) {
00648     addrSpec = QByteArray();
00649     kDebug() // << "\n"
00650       << "Input: aStr\nError:"
00651       << emailParseResultToString( result );
00652   }
00653 
00654   return addrSpec;
00655 }
00656 
00657 //-----------------------------------------------------------------------------
00658 QString KPIMUtils::extractEmailAddress( const QString &address )
00659 {
00660   return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) );
00661 }
00662 
00663 //-----------------------------------------------------------------------------
00664 QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses )
00665 {
00666   QByteArray dummy1, dummy2, addrSpec;
00667   EmailParseResult result =
00668     splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
00669                           true/* allow multiple addresses */ );
00670   if ( result != AddressOk ) {
00671     addrSpec = QByteArray();
00672     kDebug() // << "\n"
00673       << "Input: aStr\nError:"
00674       << emailParseResultToString( result );
00675   }
00676 
00677   return addrSpec;
00678 }
00679 
00680 //-----------------------------------------------------------------------------
00681 QString KPIMUtils::firstEmailAddress( const QString &addresses )
00682 {
00683   return QString::fromUtf8( firstEmailAddress( addresses.toUtf8() ) );
00684 }
00685 
00686 //-----------------------------------------------------------------------------
00687 bool KPIMUtils::extractEmailAddressAndName( const QString &aStr,
00688                                             QString &mail, QString &name )
00689 {
00690   name.clear();
00691   mail.clear();
00692 
00693   const int len = aStr.length();
00694   const char cQuotes = '"';
00695 
00696   bool bInComment = false;
00697   bool bInQuotesOutsideOfEmail = false;
00698   int i=0, iAd=0, iMailStart=0, iMailEnd=0;
00699   QChar c;
00700   unsigned int commentstack = 0;
00701 
00702   // Find the '@' of the email address
00703   // skipping all '@' inside "(...)" comments:
00704   while ( i < len ) {
00705     c = aStr[i];
00706     if ( '(' == c ) {
00707       commentstack++;
00708     }
00709     if ( ')' == c ) {
00710       commentstack--;
00711     }
00712     bInComment = commentstack != 0;
00713     if ( '"' == c && !bInComment ) {
00714       bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
00715     }
00716 
00717     if( !bInComment && !bInQuotesOutsideOfEmail ) {
00718       if ( '@' == c ) {
00719         iAd = i;
00720         break; // found it
00721       }
00722     }
00723     ++i;
00724   }
00725 
00726   if ( !iAd ) {
00727     // We suppose the user is typing the string manually and just
00728     // has not finished typing the mail address part.
00729     // So we take everything that's left of the '<' as name and the rest as mail
00730     for ( i = 0; len > i; ++i ) {
00731       c = aStr[i];
00732       if ( '<' != c ) {
00733         name.append( c );
00734       } else {
00735         break;
00736       }
00737     }
00738     mail = aStr.mid( i + 1 );
00739     if ( mail.endsWith( '>' ) ) {
00740       mail.truncate( mail.length() - 1 );
00741     }
00742 
00743   } else {
00744     // Loop backwards until we find the start of the string
00745     // or a ',' that is outside of a comment
00746     //          and outside of quoted text before the leading '<'.
00747     bInComment = false;
00748     bInQuotesOutsideOfEmail = false;
00749     for ( i = iAd-1; 0 <= i; --i ) {
00750       c = aStr[i];
00751       if ( bInComment ) {
00752         if ( '(' == c ) {
00753           if ( !name.isEmpty() ) {
00754             name.prepend( ' ' );
00755           }
00756           bInComment = false;
00757         } else {
00758           name.prepend( c ); // all comment stuff is part of the name
00759         }
00760       } else if ( bInQuotesOutsideOfEmail ) {
00761         if ( cQuotes == c ) {
00762           bInQuotesOutsideOfEmail = false;
00763         } else {
00764           name.prepend( c );
00765         }
00766       } else {
00767         // found the start of this addressee ?
00768         if ( ',' == c ) {
00769           break;
00770         }
00771         // stuff is before the leading '<' ?
00772         if ( iMailStart ) {
00773           if ( cQuotes == c ) {
00774             bInQuotesOutsideOfEmail = true; // end of quoted text found
00775           } else {
00776             name.prepend( c );
00777           }
00778         } else {
00779           switch ( c.toLatin1() ) {
00780           case '<':
00781             iMailStart = i;
00782             break;
00783           case ')':
00784             if ( !name.isEmpty() ) {
00785               name.prepend( ' ' );
00786             }
00787             bInComment = true;
00788             break;
00789           default:
00790             if ( ' ' != c ) {
00791               mail.prepend( c );
00792             }
00793           }
00794         }
00795       }
00796     }
00797 
00798     name = name.simplified();
00799     mail = mail.simplified();
00800 
00801     if ( mail.isEmpty() ) {
00802       return false;
00803     }
00804 
00805     mail.append( '@' );
00806 
00807     // Loop forward until we find the end of the string
00808     // or a ',' that is outside of a comment
00809     //          and outside of quoted text behind the trailing '>'.
00810     bInComment = false;
00811     bInQuotesOutsideOfEmail = false;
00812     int parenthesesNesting = 0;
00813     for ( i = iAd+1; len > i; ++i ) {
00814       c = aStr[i];
00815       if ( bInComment ) {
00816         if ( ')' == c ) {
00817           if ( --parenthesesNesting == 0 ) {
00818             bInComment = false;
00819             if ( !name.isEmpty() ) {
00820               name.append( ' ' );
00821             }
00822           } else {
00823             // nested ")", add it
00824             name.append( ')' ); // name can't be empty here
00825           }
00826         } else {
00827           if ( '(' == c ) {
00828             // nested "("
00829             ++parenthesesNesting;
00830           }
00831           name.append( c ); // all comment stuff is part of the name
00832         }
00833       } else if ( bInQuotesOutsideOfEmail ) {
00834         if ( cQuotes == c ) {
00835           bInQuotesOutsideOfEmail = false;
00836         } else {
00837           name.append( c );
00838         }
00839       } else {
00840         // found the end of this addressee ?
00841         if ( ',' == c ) {
00842           break;
00843         }
00844         // stuff is behind the trailing '>' ?
00845         if ( iMailEnd ){
00846           if ( cQuotes == c ) {
00847             bInQuotesOutsideOfEmail = true; // start of quoted text found
00848           } else {
00849             name.append( c );
00850           }
00851         } else {
00852           switch ( c.toLatin1() ) {
00853           case '>':
00854             iMailEnd = i;
00855             break;
00856           case '(':
00857             if ( !name.isEmpty() ) {
00858               name.append( ' ' );
00859             }
00860             if ( ++parenthesesNesting > 0 ) {
00861               bInComment = true;
00862             }
00863             break;
00864           default:
00865             if ( ' ' != c ) {
00866               mail.append( c );
00867             }
00868           }
00869         }
00870       }
00871     }
00872   }
00873 
00874   name = name.simplified();
00875   mail = mail.simplified();
00876 
00877   return ! ( name.isEmpty() || mail.isEmpty() );
00878 }
00879 
00880 //-----------------------------------------------------------------------------
00881 bool KPIMUtils::compareEmail( const QString &email1, const QString &email2,
00882                               bool matchName )
00883 {
00884   QString e1Name, e1Email, e2Name, e2Email;
00885 
00886   extractEmailAddressAndName( email1, e1Email, e1Name );
00887   extractEmailAddressAndName( email2, e2Email, e2Name );
00888 
00889   return e1Email == e2Email &&
00890     ( !matchName || ( e1Name == e2Name ) );
00891 }
00892 
00893 //-----------------------------------------------------------------------------
00894 QString KPIMUtils::normalizedAddress( const QString &displayName,
00895                                       const QString &addrSpec,
00896                                       const QString &comment )
00897 {
00898   if ( displayName.isEmpty() && comment.isEmpty() ) {
00899     return addrSpec;
00900   } else if ( comment.isEmpty() ) {
00901     if ( !displayName.startsWith('\"') ) {
00902       return quoteNameIfNecessary( displayName ) + " <" + addrSpec + '>';
00903     } else {
00904       return displayName + " <" + addrSpec + '>';
00905     }
00906   } else if ( displayName.isEmpty() ) {
00907     QString commentStr = comment;
00908     return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + '>';
00909   } else {
00910     return displayName + " (" + comment + ") <" + addrSpec + '>';
00911   }
00912 }
00913 
00914 //-----------------------------------------------------------------------------
00915 QString KPIMUtils::fromIdn( const QString &addrSpec )
00916 {
00917   const int atPos = addrSpec.lastIndexOf( '@' );
00918   if ( atPos == -1 ) {
00919     return addrSpec;
00920   }
00921 
00922   QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() );
00923   if ( idn.isEmpty() ) {
00924     return QString();
00925   }
00926 
00927   return addrSpec.left( atPos + 1 ) + idn;
00928 }
00929 
00930 //-----------------------------------------------------------------------------
00931 QString KPIMUtils::toIdn( const QString &addrSpec )
00932 {
00933   const int atPos = addrSpec.lastIndexOf( '@' );
00934   if ( atPos == -1 ) {
00935     return addrSpec;
00936   }
00937 
00938   QString idn = KUrl::toAce( addrSpec.mid( atPos + 1 ) );
00939   if ( idn.isEmpty() ) {
00940     return addrSpec;
00941   }
00942 
00943   return addrSpec.left( atPos + 1 ) + idn;
00944 }
00945 
00946 //-----------------------------------------------------------------------------
00947 QString KPIMUtils::normalizeAddressesAndDecodeIdn( const QString &str )
00948 {
00949   //  kDebug() << str;
00950   if ( str.isEmpty() ) {
00951     return str;
00952   }
00953 
00954   const QStringList addressList = splitAddressList( str );
00955   QStringList normalizedAddressList;
00956 
00957   QByteArray displayName, addrSpec, comment;
00958 
00959   for ( QStringList::ConstIterator it = addressList.begin();
00960         ( it != addressList.end() );
00961         ++it ) {
00962     if ( !(*it).isEmpty() ) {
00963       if ( splitAddress( (*it).toUtf8(),
00964                          displayName, addrSpec, comment ) == AddressOk ) {
00965 
00966         displayName = KMime::decodeRFC2047String(displayName).toUtf8();
00967         comment = KMime::decodeRFC2047String(comment).toUtf8();
00968 
00969         normalizedAddressList
00970           << normalizedAddress( QString::fromUtf8( displayName ),
00971                                 fromIdn( QString::fromUtf8( addrSpec ) ),
00972                                 QString::fromUtf8( comment ) );
00973       } else {
00974         kDebug() << "splitting address failed:" << *it;
00975       }
00976     }
00977   }
00978   /*
00979     kDebug() << "normalizedAddressList: \""
00980              << normalizedAddressList.join( ", " )
00981              << "\"";
00982   */
00983   return normalizedAddressList.join( ", " );
00984 }
00985 
00986 //-----------------------------------------------------------------------------
00987 QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str )
00988 {
00989   //kDebug() << str;
00990   if ( str.isEmpty() ) {
00991     return str;
00992   }
00993 
00994   const QStringList addressList = splitAddressList( str );
00995   QStringList normalizedAddressList;
00996 
00997   QByteArray displayName, addrSpec, comment;
00998 
00999   for ( QStringList::ConstIterator it = addressList.begin();
01000         ( it != addressList.end() );
01001         ++it ) {
01002     if ( !(*it).isEmpty() ) {
01003       if ( splitAddress( (*it).toUtf8(),
01004                          displayName, addrSpec, comment ) == AddressOk ) {
01005 
01006         normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ),
01007                                                     toIdn( QString::fromUtf8( addrSpec ) ),
01008                                                     QString::fromUtf8( comment ) );
01009       } else {
01010         kDebug() << "splitting address failed:" << *it;
01011       }
01012     }
01013   }
01014 
01015   /*
01016     kDebug() << "normalizedAddressList: \""
01017              << normalizedAddressList.join( ", " )
01018              << "\"";
01019   */
01020   return normalizedAddressList.join( ", " );
01021 }
01022 
01023 //-----------------------------------------------------------------------------
01024 // Escapes unescaped doublequotes in str.
01025 static QString escapeQuotes( const QString &str )
01026 {
01027   if ( str.isEmpty() ) {
01028     return QString();
01029   }
01030 
01031   QString escaped;
01032   // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
01033   escaped.reserve( 2 * str.length() );
01034   unsigned int len = 0;
01035   for ( int i = 0; i < str.length(); ++i, ++len ) {
01036     if ( str[i] == '"' ) { // unescaped doublequote
01037       escaped[len] = '\\';
01038       ++len;
01039     } else if ( str[i] == '\\' ) { // escaped character
01040       escaped[len] = '\\';
01041       ++len;
01042       ++i;
01043       if ( i >= str.length() ) { // handle trailing '\' gracefully
01044         break;
01045       }
01046     }
01047     escaped[len] = str[i];
01048   }
01049   escaped.truncate( len );
01050   return escaped;
01051 }
01052 
01053 //-----------------------------------------------------------------------------
01054 QString KPIMUtils::quoteNameIfNecessary( const QString &str )
01055 {
01056   QString quoted = str;
01057 
01058   QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
01059   // avoid double quoting
01060   if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) {
01061     quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\"";
01062   } else if ( quoted.indexOf( needQuotes ) != -1 ) {
01063     quoted = "\"" + escapeQuotes( quoted ) + "\"";
01064   }
01065 
01066   return quoted;
01067 }

kpimutils

Skip menu "kpimutils"
  • Main Page
  • Modules
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  • kabc
  • kblog
  • kcal
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal