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

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.6.1
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