• Skip to content
  • Skip to link menu
KDE 4.3 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           return list;
00082         }
00083       }
00084       break;
00085     case '\\' : // quoted character
00086       index++; // ignore the quoted character
00087       break;
00088     case ',' :
00089       if ( !insidequote && ( commentlevel == 0 ) ) {
00090         addr = aStr.mid( addrstart, index - addrstart );
00091         if ( !addr.isEmpty() ) {
00092           list += addr.simplified();
00093         }
00094         addrstart = index + 1;
00095       }
00096       break;
00097     }
00098   }
00099   // append the last address to the list
00100   if ( !insidequote && ( commentlevel == 0 ) ) {
00101     addr = aStr.mid( addrstart, aStr.length() - addrstart );
00102     if ( !addr.isEmpty() ) {
00103       list += addr.simplified();
00104     }
00105   }
00106 
00107   return list;
00108 }
00109 
00110 //-----------------------------------------------------------------------------
00111 // Used by KPIMUtils::splitAddress(...) and KPIMUtils::firstEmailAddress(...).
00112 KPIMUtils::EmailParseResult splitAddressInternal( const QByteArray  address,
00113                                                   QByteArray &displayName,
00114                                                   QByteArray &addrSpec,
00115                                                   QByteArray &comment,
00116                                                   bool allowMultipleAddresses )
00117 {
00118   //  kDebug() << "address";
00119   displayName = "";
00120   addrSpec = "";
00121   comment = "";
00122 
00123   if ( address.isEmpty() ) {
00124     return AddressEmpty;
00125   }
00126 
00127   // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
00128   // The purpose is to extract a displayable string from the mailboxes.
00129   // Comments in the addr-spec are not handled. No error checking is done.
00130 
00131   enum {
00132     TopLevel,
00133     InComment,
00134     InAngleAddress
00135   } context = TopLevel;
00136   bool inQuotedString = false;
00137   int commentLevel = 0;
00138   bool stop = false;
00139 
00140   for ( const char *p = address.data(); *p && !stop; ++p ) {
00141     switch ( context ) {
00142     case TopLevel :
00143       {
00144       switch ( *p ) {
00145       case '"' :
00146         inQuotedString = !inQuotedString;
00147         displayName += *p;
00148         break;
00149       case '(' :
00150         if ( !inQuotedString ) {
00151           context = InComment;
00152           commentLevel = 1;
00153         } else {
00154           displayName += *p;
00155         }
00156         break;
00157       case '<' :
00158         if ( !inQuotedString ) {
00159           context = InAngleAddress;
00160         } else {
00161           displayName += *p;
00162         }
00163         break;
00164       case '\\' : // quoted character
00165         displayName += *p;
00166         ++p; // skip the '\'
00167         if ( *p ) {
00168           displayName += *p;
00169         } else {
00170           return UnexpectedEnd;
00171         }
00172         break;
00173       case ',' :
00174         if ( !inQuotedString ) {
00175           if ( allowMultipleAddresses ) {
00176             stop = true;
00177           } else {
00178             return UnexpectedComma;
00179           }
00180         } else {
00181           displayName += *p;
00182         }
00183         break;
00184       default :
00185         displayName += *p;
00186       }
00187       break;
00188       }
00189     case InComment :
00190       {
00191       switch ( *p ) {
00192       case '(' :
00193         ++commentLevel;
00194         comment += *p;
00195         break;
00196       case ')' :
00197         --commentLevel;
00198         if ( commentLevel == 0 ) {
00199           context = TopLevel;
00200           comment += ' '; // separate the text of several comments
00201         } else {
00202           comment += *p;
00203         }
00204         break;
00205       case '\\' : // quoted character
00206         comment += *p;
00207         ++p; // skip the '\'
00208         if ( *p ) {
00209           comment += *p;
00210         } else {
00211           return UnexpectedEnd;
00212         }
00213         break;
00214       default :
00215         comment += *p;
00216       }
00217       break;
00218       }
00219     case InAngleAddress :
00220       {
00221         switch ( *p ) {
00222       case '"' :
00223         inQuotedString = !inQuotedString;
00224         addrSpec += *p;
00225         break;
00226       case '>' :
00227         if ( !inQuotedString ) {
00228           context = TopLevel;
00229         } else {
00230           addrSpec += *p;
00231         }
00232         break;
00233       case '\\' : // quoted character
00234         addrSpec += *p;
00235         ++p; // skip the '\'
00236         if ( *p ) {
00237           addrSpec += *p;
00238         } else {
00239           return UnexpectedEnd;
00240         }
00241         break;
00242       default :
00243         addrSpec += *p;
00244       }
00245       break;
00246     }
00247     } // switch ( context )
00248   }
00249   // check for errors
00250   if ( inQuotedString ) {
00251     return UnbalancedQuote;
00252   }
00253   if ( context == InComment ) {
00254     return UnbalancedParens;
00255   }
00256   if ( context == InAngleAddress ) {
00257     return UnclosedAngleAddr;
00258   }
00259 
00260   displayName = displayName.trimmed();
00261   comment = comment.trimmed();
00262   addrSpec = addrSpec.trimmed();
00263 
00264   if ( addrSpec.isEmpty() ) {
00265     if ( displayName.isEmpty() ) {
00266       return NoAddressSpec;
00267     } else {
00268       addrSpec = displayName;
00269       displayName.truncate( 0 );
00270     }
00271   }
00272   /*
00273     kDebug() << "display-name : \"" << displayName << "\"";
00274     kDebug() << "comment      : \"" << comment << "\"";
00275     kDebug() << "addr-spec    : \"" << addrSpec << "\"";
00276   */
00277   return AddressOk;
00278 }
00279 
00280 //-----------------------------------------------------------------------------
00281 EmailParseResult KPIMUtils::splitAddress( const QByteArray &address,
00282                                           QByteArray &displayName,
00283                                           QByteArray &addrSpec,
00284                                           QByteArray &comment )
00285 {
00286   return splitAddressInternal( address, displayName, addrSpec, comment,
00287                                false/* don't allow multiple addresses */ );
00288 }
00289 
00290 //-----------------------------------------------------------------------------
00291 EmailParseResult KPIMUtils::splitAddress( const QString &address,
00292                                           QString &displayName,
00293                                           QString &addrSpec,
00294                                           QString &comment )
00295 {
00296   QByteArray d, a, c;
00297   EmailParseResult result = splitAddress( address.toUtf8(), d, a, c );
00298 
00299   if ( result == AddressOk ) {
00300     displayName = QString::fromUtf8( d );
00301     addrSpec = QString::fromUtf8( a );
00302     comment = QString::fromUtf8( c );
00303   }
00304   return result;
00305 }
00306 
00307 //-----------------------------------------------------------------------------
00308 EmailParseResult KPIMUtils::isValidAddress( const QString &aStr )
00309 {
00310   // If we are passed an empty string bail right away no need to process
00311   // further and waste resources
00312   if ( aStr.isEmpty() ) {
00313     return AddressEmpty;
00314   }
00315 
00316   // count how many @'s are in the string that is passed to us
00317   // if 0 or > 1 take action
00318   // at this point to many @'s cannot bail out right away since
00319   // @ is allowed in qoutes, so we use a bool to keep track
00320   // and then make a judgment further down in the parser
00321   // FIXME count only @ not in double quotes
00322 
00323   bool tooManyAtsFlag = false;
00324 
00325   int atCount = aStr.count( '@' );
00326   if ( atCount > 1 ) {
00327     tooManyAtsFlag = true;
00328   } else if ( atCount == 0 ) {
00329     return TooFewAts;
00330   }
00331 
00332   // The main parser, try and catch all weird and wonderful
00333   // mistakes users and/or machines can create
00334 
00335   enum {
00336     TopLevel,
00337     InComment,
00338     InAngleAddress
00339   } context = TopLevel;
00340   bool inQuotedString = false;
00341   int commentLevel = 0;
00342 
00343   unsigned int strlen = aStr.length();
00344 
00345   for ( unsigned int index=0; index < strlen; index++ ) {
00346     switch ( context ) {
00347     case TopLevel :
00348       {
00349         switch ( aStr[index].toLatin1() ) {
00350         case '"' :
00351           inQuotedString = !inQuotedString;
00352           break;
00353         case '(' :
00354           if ( !inQuotedString ) {
00355             context = InComment;
00356             commentLevel = 1;
00357           }
00358           break;
00359         case '[' :
00360           if ( !inQuotedString ) {
00361             return InvalidDisplayName;
00362           }
00363           break;
00364         case ']' :
00365           if ( !inQuotedString ) {
00366             return InvalidDisplayName;
00367           }
00368           break;
00369         case ':' :
00370           if ( !inQuotedString ) {
00371             return DisallowedChar;
00372           }
00373           break;
00374         case '<' :
00375           if ( !inQuotedString ) {
00376             context = InAngleAddress;
00377           }
00378           break;
00379         case '\\' : // quoted character
00380           ++index; // skip the '\'
00381           if ( ( index + 1 ) > strlen ) {
00382             return UnexpectedEnd;
00383           }
00384           break;
00385         case ',' :
00386           if ( !inQuotedString ) {
00387             return UnexpectedComma;
00388           }
00389           break;
00390         case ')' :
00391           if ( !inQuotedString ) {
00392             return UnbalancedParens;
00393           }
00394           break;
00395         case '>' :
00396           if ( !inQuotedString ) {
00397             return UnopenedAngleAddr;
00398           }
00399           break;
00400         case '@' :
00401           if ( !inQuotedString ) {
00402             if ( index == 0 ) {  // Missing local part
00403               return MissingLocalPart;
00404             } else if ( index == strlen-1 ) {
00405               return MissingDomainPart;
00406               break;
00407             }
00408           } else if ( inQuotedString ) {
00409             --atCount;
00410             if ( atCount == 1 ) {
00411               tooManyAtsFlag = false;
00412             }
00413           }
00414           break;
00415         }
00416         break;
00417       }
00418     case InComment :
00419       {
00420         switch ( aStr[index].toLatin1() ) {
00421         case '(' :
00422           ++commentLevel;
00423           break;
00424         case ')' :
00425           --commentLevel;
00426           if ( commentLevel == 0 ) {
00427             context = TopLevel;
00428           }
00429           break;
00430         case '\\' : // quoted character
00431           ++index; // skip the '\'
00432           if ( ( index + 1 ) > strlen ) {
00433             return UnexpectedEnd;
00434           }
00435           break;
00436         }
00437         break;
00438       }
00439 
00440     case InAngleAddress :
00441       {
00442         switch ( aStr[index].toLatin1() ) {
00443         case ',' :
00444           if ( !inQuotedString ) {
00445             return UnexpectedComma;
00446           }
00447           break;
00448         case '"' :
00449           inQuotedString = !inQuotedString;
00450           break;
00451         case '@' :
00452           if ( inQuotedString ) {
00453             --atCount;
00454             if ( atCount == 1 ) {
00455               tooManyAtsFlag = false;
00456             }
00457           }
00458           break;
00459         case '>' :
00460           if ( !inQuotedString ) {
00461             context = TopLevel;
00462             break;
00463           }
00464           break;
00465         case '\\' : // quoted character
00466           ++index; // skip the '\'
00467           if ( ( index + 1 ) > strlen ) {
00468             return UnexpectedEnd;
00469           }
00470           break;
00471         }
00472         break;
00473       }
00474     }
00475   }
00476 
00477   if ( atCount == 0 && !inQuotedString ) {
00478     return TooFewAts;
00479   }
00480 
00481   if ( inQuotedString ) {
00482     return UnbalancedQuote;
00483   }
00484 
00485   if ( context == InComment ) {
00486     return UnbalancedParens;
00487   }
00488 
00489   if ( context == InAngleAddress ) {
00490     return UnclosedAngleAddr;
00491   }
00492 
00493   if ( tooManyAtsFlag ) {
00494     return TooManyAts;
00495   }
00496 
00497   return AddressOk;
00498 }
00499 
00500 //-----------------------------------------------------------------------------
00501 KPIMUtils::EmailParseResult KPIMUtils::isValidAddressList( const QString &aStr,
00502                                                            QString &badAddr )
00503 {
00504   if ( aStr.isEmpty() ) {
00505     return AddressEmpty;
00506   }
00507 
00508   const QStringList list = splitAddressList( aStr );
00509 
00510   QStringList::const_iterator it = list.begin();
00511   EmailParseResult errorCode = AddressOk;
00512   for ( it = list.begin(); it != list.end(); ++it ) {
00513     errorCode = isValidAddress( *it );
00514     if ( errorCode != AddressOk ) {
00515       badAddr = ( *it );
00516       break;
00517     }
00518   }
00519   return errorCode;
00520 }
00521 
00522 //-----------------------------------------------------------------------------
00523 QString KPIMUtils::emailParseResultToString( EmailParseResult errorCode )
00524 {
00525   switch ( errorCode ) {
00526   case TooManyAts :
00527     return i18n( "The email address you entered is not valid because it "
00528                  "contains more than one @. "
00529                  "You will not create valid messages if you do not "
00530                  "change your address." );
00531   case TooFewAts :
00532     return i18n( "The email address you entered is not valid because it "
00533                  "does not contain a @."
00534                  "You will not create valid messages if you do not "
00535                  "change your address." );
00536   case AddressEmpty :
00537     return i18n( "You have to enter something in the email address field." );
00538   case MissingLocalPart :
00539     return i18n( "The email address you entered is not valid because it "
00540                  "does not contain a local part." );
00541   case MissingDomainPart :
00542     return i18n( "The email address you entered is not valid because it "
00543                  "does not contain a domain part." );
00544   case UnbalancedParens :
00545     return i18n( "The email address you entered is not valid because it "
00546                  "contains unclosed comments/brackets." );
00547   case AddressOk :
00548     return i18n( "The email address you entered is valid." );
00549   case UnclosedAngleAddr :
00550     return i18n( "The email address you entered is not valid because it "
00551                  "contains an unclosed angle bracket." );
00552   case UnopenedAngleAddr :
00553     return i18n( "The email address you entered is not valid because it "
00554                  "contains too many closing angle brackets." );
00555   case UnexpectedComma :
00556     return i18n( "The email address you have entered is not valid because it "
00557                  "contains an unexpected comma." );
00558   case UnexpectedEnd :
00559     return i18n( "The email address you entered is not valid because it ended "
00560                  "unexpectedly. This probably means you have used an escaping "
00561                  "type character like a '\\' as the last character in your "
00562                  "email address." );
00563   case UnbalancedQuote :
00564     return i18n( "The email address you entered is not valid because it "
00565                  "contains quoted text which does not end." );
00566   case NoAddressSpec :
00567     return i18n( "The email address you entered is not valid because it "
00568                  "does not seem to contain an actual email address, i.e. "
00569                  "something of the form joe@example.org." );
00570   case DisallowedChar :
00571     return i18n( "The email address you entered is not valid because it "
00572                  "contains an illegal character." );
00573   case InvalidDisplayName :
00574     return i18n( "The email address you have entered is not valid because it "
00575                  "contains an invalid display name." );
00576   }
00577   return i18n( "Unknown problem with email address" );
00578 }
00579 
00580 //-----------------------------------------------------------------------------
00581 bool KPIMUtils::isValidSimpleAddress( const QString &aStr )
00582 {
00583   // If we are passed an empty string bail right away no need to process further
00584   // and waste resources
00585   if ( aStr.isEmpty() ) {
00586     return false;
00587   }
00588 
00589   int atChar = aStr.lastIndexOf( '@' );
00590   QString domainPart = aStr.mid( atChar + 1 );
00591   QString localPart = aStr.left( atChar );
00592   bool tooManyAtsFlag = false;
00593   bool inQuotedString = false;
00594   int atCount = localPart.count( '@' );
00595 
00596   unsigned int strlen = localPart.length();
00597   for ( unsigned int index=0; index < strlen; index++ ) {
00598     switch( localPart[ index ].toLatin1() ) {
00599     case '"' :
00600       inQuotedString = !inQuotedString;
00601       break;
00602     case '@' :
00603       if ( inQuotedString ) {
00604         --atCount;
00605         if ( atCount == 0 ) {
00606           tooManyAtsFlag = false;
00607         }
00608       }
00609       break;
00610     }
00611   }
00612 
00613   QString addrRx =
00614     "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
00615 
00616   if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) {
00617     addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@";
00618   }
00619   if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) {
00620     addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]";
00621   } else {
00622     addrRx += "[\\w-]+(\\.[\\w-]+)*";
00623   }
00624   QRegExp rx( addrRx );
00625   return  rx.exactMatch( aStr ) && !tooManyAtsFlag;
00626 }
00627 
00628 //-----------------------------------------------------------------------------
00629 QString KPIMUtils::simpleEmailAddressErrorMsg()
00630 {
00631   return i18n( "The email address you entered is not valid because it "
00632                "does not seem to contain an actual email address, i.e. "
00633                "something of the form joe@example.org." );
00634 }
00635 
00636 //-----------------------------------------------------------------------------
00637 QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address )
00638 {
00639   QByteArray dummy1, dummy2, addrSpec;
00640   EmailParseResult result =
00641     splitAddressInternal( address, dummy1, addrSpec, dummy2,
00642                           false/* don't allow multiple addresses */ );
00643   if ( result != AddressOk ) {
00644     addrSpec = QByteArray();
00645     if ( result != AddressEmpty ) {
00646       kDebug()
00647         << "Input: aStr\nError:"
00648         << emailParseResultToString( result );
00649     }
00650   }
00651 
00652   return addrSpec;
00653 }
00654 
00655 //-----------------------------------------------------------------------------
00656 QString KPIMUtils::extractEmailAddress( const QString &address )
00657 {
00658   return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) );
00659 }
00660 
00661 //-----------------------------------------------------------------------------
00662 QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses )
00663 {
00664   QByteArray dummy1, dummy2, addrSpec;
00665   EmailParseResult result =
00666     splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
00667                           true/* allow multiple addresses */ );
00668   if ( result != AddressOk ) {
00669     addrSpec = QByteArray();
00670     if ( result != AddressEmpty ) {
00671       kDebug()
00672         << "Input: aStr\nError:"
00673         << emailParseResultToString( result );
00674     }
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       }
00974     }
00975   }
00976   /*
00977     kDebug() << "normalizedAddressList: \""
00978              << normalizedAddressList.join( ", " )
00979              << "\"";
00980   */
00981   return normalizedAddressList.join( ", " );
00982 }
00983 
00984 //-----------------------------------------------------------------------------
00985 QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str )
00986 {
00987   //kDebug() << str;
00988   if ( str.isEmpty() ) {
00989     return str;
00990   }
00991 
00992   const QStringList addressList = splitAddressList( str );
00993   QStringList normalizedAddressList;
00994 
00995   QByteArray displayName, addrSpec, comment;
00996 
00997   for ( QStringList::ConstIterator it = addressList.begin();
00998         ( it != addressList.end() );
00999         ++it ) {
01000     if ( !(*it).isEmpty() ) {
01001       if ( splitAddress( (*it).toUtf8(),
01002                          displayName, addrSpec, comment ) == AddressOk ) {
01003 
01004         normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ),
01005                                                     toIdn( QString::fromUtf8( addrSpec ) ),
01006                                                     QString::fromUtf8( comment ) );
01007       }
01008     }
01009   }
01010 
01011   /*
01012     kDebug() << "normalizedAddressList: \""
01013              << normalizedAddressList.join( ", " )
01014              << "\"";
01015   */
01016   return normalizedAddressList.join( ", " );
01017 }
01018 
01019 //-----------------------------------------------------------------------------
01020 // Escapes unescaped doublequotes in str.
01021 static QString escapeQuotes( const QString &str )
01022 {
01023   if ( str.isEmpty() ) {
01024     return QString();
01025   }
01026 
01027   QString escaped;
01028   // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
01029   escaped.reserve( 2 * str.length() );
01030   unsigned int len = 0;
01031   for ( int i = 0; i < str.length(); ++i, ++len ) {
01032     if ( str[i] == '"' ) { // unescaped doublequote
01033       escaped[len] = '\\';
01034       ++len;
01035     } else if ( str[i] == '\\' ) { // escaped character
01036       escaped[len] = '\\';
01037       ++len;
01038       ++i;
01039       if ( i >= str.length() ) { // handle trailing '\' gracefully
01040         break;
01041       }
01042     }
01043     escaped[len] = str[i];
01044   }
01045   escaped.truncate( len );
01046   return escaped;
01047 }
01048 
01049 //-----------------------------------------------------------------------------
01050 QString KPIMUtils::quoteNameIfNecessary( const QString &str )
01051 {
01052   QString quoted = str;
01053 
01054   QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
01055   // avoid double quoting
01056   if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) {
01057     quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\"";
01058   } else if ( quoted.indexOf( needQuotes ) != -1 ) {
01059     quoted = "\"" + escapeQuotes( quoted ) + "\"";
01060   }
01061 
01062   return quoted;
01063 }

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
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  • kldap
  • kmime
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.5.8
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