• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • 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 */
00027 #include "email.h"
00028 
00029 #include <kmime/kmime_util.h>
00030 
00031 #include <KDebug>
00032 #include <KLocale>
00033 #include <KUrl>
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 
00594   // Both of these parts must be non empty
00595   // after all we cannot have emails like:
00596   // @kde.org, or  foo@
00597   if ( localPart.isEmpty() || domainPart.isEmpty() ) {
00598     return false;
00599   }
00600 
00601   bool tooManyAtsFlag = false;
00602   bool inQuotedString = false;
00603   int atCount = localPart.count( '@' );
00604 
00605   unsigned int strlen = localPart.length();
00606   for ( unsigned int index=0; index < strlen; index++ ) {
00607     switch( localPart[ index ].toLatin1() ) {
00608     case '"' :
00609       inQuotedString = !inQuotedString;
00610       break;
00611     case '@' :
00612       if ( inQuotedString ) {
00613         --atCount;
00614         if ( atCount == 0 ) {
00615           tooManyAtsFlag = false;
00616         }
00617       }
00618       break;
00619     }
00620   }
00621 
00622   QString addrRx =
00623     "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
00624 
00625   if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) {
00626     addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@";
00627   }
00628   if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) {
00629     addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]";
00630   } else {
00631     addrRx += "[\\w-]+(\\.[\\w-]+)*";
00632   }
00633   QRegExp rx( addrRx );
00634   return  rx.exactMatch( aStr ) && !tooManyAtsFlag;
00635 }
00636 
00637 //-----------------------------------------------------------------------------
00638 QString KPIMUtils::simpleEmailAddressErrorMsg()
00639 {
00640   return i18n( "The email address you entered is not valid because it "
00641                "does not seem to contain an actual email address, i.e. "
00642                "something of the form joe@example.org." );
00643 }
00644 
00645 //-----------------------------------------------------------------------------
00646 QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address )
00647 {
00648   QByteArray dummy1, dummy2, addrSpec;
00649   EmailParseResult result =
00650     splitAddressInternal( address, dummy1, addrSpec, dummy2,
00651                           false/* don't allow multiple addresses */ );
00652   if ( result != AddressOk ) {
00653     addrSpec = QByteArray();
00654     if ( result != AddressEmpty ) {
00655       kDebug()
00656         << "Input:" << address << "\nError:"
00657         << emailParseResultToString( result );
00658     }
00659   }
00660 
00661   return addrSpec;
00662 }
00663 
00664 //-----------------------------------------------------------------------------
00665 QString KPIMUtils::extractEmailAddress( const QString &address )
00666 {
00667   return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) );
00668 }
00669 
00670 //-----------------------------------------------------------------------------
00671 QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses )
00672 {
00673   QByteArray dummy1, dummy2, addrSpec;
00674   EmailParseResult result =
00675     splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
00676                           true/* allow multiple addresses */ );
00677   if ( result != AddressOk ) {
00678     addrSpec = QByteArray();
00679     if ( result != AddressEmpty ) {
00680       kDebug()
00681         << "Input: aStr\nError:"
00682         << emailParseResultToString( result );
00683     }
00684   }
00685 
00686   return addrSpec;
00687 }
00688 
00689 //-----------------------------------------------------------------------------
00690 QString KPIMUtils::firstEmailAddress( const QString &addresses )
00691 {
00692   return QString::fromUtf8( firstEmailAddress( addresses.toUtf8() ) );
00693 }
00694 
00695 //-----------------------------------------------------------------------------
00696 bool KPIMUtils::extractEmailAddressAndName( const QString &aStr,
00697                                             QString &mail, QString &name )
00698 {
00699   name.clear();
00700   mail.clear();
00701 
00702   const int len = aStr.length();
00703   const char cQuotes = '"';
00704 
00705   bool bInComment = false;
00706   bool bInQuotesOutsideOfEmail = false;
00707   int i=0, iAd=0, iMailStart=0, iMailEnd=0;
00708   QChar c;
00709   unsigned int commentstack = 0;
00710 
00711   // Find the '@' of the email address
00712   // skipping all '@' inside "(...)" comments:
00713   while ( i < len ) {
00714     c = aStr[i];
00715     if ( '(' == c ) {
00716       commentstack++;
00717     }
00718     if ( ')' == c ) {
00719       commentstack--;
00720     }
00721     bInComment = commentstack != 0;
00722     if ( '"' == c && !bInComment ) {
00723       bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
00724     }
00725 
00726     if( !bInComment && !bInQuotesOutsideOfEmail ) {
00727       if ( '@' == c ) {
00728         iAd = i;
00729         break; // found it
00730       }
00731     }
00732     ++i;
00733   }
00734 
00735   if ( !iAd ) {
00736     // We suppose the user is typing the string manually and just
00737     // has not finished typing the mail address part.
00738     // So we take everything that's left of the '<' as name and the rest as mail
00739     for ( i = 0; len > i; ++i ) {
00740       c = aStr[i];
00741       if ( '<' != c ) {
00742         name.append( c );
00743       } else {
00744         break;
00745       }
00746     }
00747     mail = aStr.mid( i + 1 );
00748     if ( mail.endsWith( '>' ) ) {
00749       mail.truncate( mail.length() - 1 );
00750     }
00751 
00752   } else {
00753     // Loop backwards until we find the start of the string
00754     // or a ',' that is outside of a comment
00755     //          and outside of quoted text before the leading '<'.
00756     bInComment = false;
00757     bInQuotesOutsideOfEmail = false;
00758     for ( i = iAd-1; 0 <= i; --i ) {
00759       c = aStr[i];
00760       if ( bInComment ) {
00761         if ( '(' == c ) {
00762           if ( !name.isEmpty() ) {
00763             name.prepend( ' ' );
00764           }
00765           bInComment = false;
00766         } else {
00767           name.prepend( c ); // all comment stuff is part of the name
00768         }
00769       } else if ( bInQuotesOutsideOfEmail ) {
00770         if ( cQuotes == c ) {
00771           bInQuotesOutsideOfEmail = false;
00772         } else if ( c != '\\' ) {
00773           name.prepend( c );
00774         }
00775       } else {
00776         // found the start of this addressee ?
00777         if ( ',' == c ) {
00778           break;
00779         }
00780         // stuff is before the leading '<' ?
00781         if ( iMailStart ) {
00782           if ( cQuotes == c ) {
00783             bInQuotesOutsideOfEmail = true; // end of quoted text found
00784           } else {
00785             name.prepend( c );
00786           }
00787         } else {
00788           switch ( c.toLatin1() ) {
00789           case '<':
00790             iMailStart = i;
00791             break;
00792           case ')':
00793             if ( !name.isEmpty() ) {
00794               name.prepend( ' ' );
00795             }
00796             bInComment = true;
00797             break;
00798           default:
00799             if ( ' ' != c ) {
00800               mail.prepend( c );
00801             }
00802           }
00803         }
00804       }
00805     }
00806 
00807     name = name.simplified();
00808     mail = mail.simplified();
00809 
00810     if ( mail.isEmpty() ) {
00811       return false;
00812     }
00813 
00814     mail.append( '@' );
00815 
00816     // Loop forward until we find the end of the string
00817     // or a ',' that is outside of a comment
00818     //          and outside of quoted text behind the trailing '>'.
00819     bInComment = false;
00820     bInQuotesOutsideOfEmail = false;
00821     int parenthesesNesting = 0;
00822     for ( i = iAd+1; len > i; ++i ) {
00823       c = aStr[i];
00824       if ( bInComment ) {
00825         if ( ')' == c ) {
00826           if ( --parenthesesNesting == 0 ) {
00827             bInComment = false;
00828             if ( !name.isEmpty() ) {
00829               name.append( ' ' );
00830             }
00831           } else {
00832             // nested ")", add it
00833             name.append( ')' ); // name can't be empty here
00834           }
00835         } else {
00836           if ( '(' == c ) {
00837             // nested "("
00838             ++parenthesesNesting;
00839           }
00840           name.append( c ); // all comment stuff is part of the name
00841         }
00842       } else if ( bInQuotesOutsideOfEmail ) {
00843         if ( cQuotes == c ) {
00844           bInQuotesOutsideOfEmail = false;
00845         } else if ( c != '\\' ) {
00846           name.append( c );
00847         }
00848       } else {
00849         // found the end of this addressee ?
00850         if ( ',' == c ) {
00851           break;
00852         }
00853         // stuff is behind the trailing '>' ?
00854         if ( iMailEnd ){
00855           if ( cQuotes == c ) {
00856             bInQuotesOutsideOfEmail = true; // start of quoted text found
00857           } else {
00858             name.append( c );
00859           }
00860         } else {
00861           switch ( c.toLatin1() ) {
00862           case '>':
00863             iMailEnd = i;
00864             break;
00865           case '(':
00866             if ( !name.isEmpty() ) {
00867               name.append( ' ' );
00868             }
00869             if ( ++parenthesesNesting > 0 ) {
00870               bInComment = true;
00871             }
00872             break;
00873           default:
00874             if ( ' ' != c ) {
00875               mail.append( c );
00876             }
00877           }
00878         }
00879       }
00880     }
00881   }
00882 
00883   name = name.simplified();
00884   mail = mail.simplified();
00885 
00886   return ! ( name.isEmpty() || mail.isEmpty() );
00887 }
00888 
00889 //-----------------------------------------------------------------------------
00890 bool KPIMUtils::compareEmail( const QString &email1, const QString &email2,
00891                               bool matchName )
00892 {
00893   QString e1Name, e1Email, e2Name, e2Email;
00894 
00895   extractEmailAddressAndName( email1, e1Email, e1Name );
00896   extractEmailAddressAndName( email2, e2Email, e2Name );
00897 
00898   return e1Email == e2Email &&
00899     ( !matchName || ( e1Name == e2Name ) );
00900 }
00901 
00902 //-----------------------------------------------------------------------------
00903 QString KPIMUtils::normalizedAddress( const QString &displayName,
00904                                       const QString &addrSpec,
00905                                       const QString &comment )
00906 {
00907   const QString realDisplayName = KMime::removeBidiControlChars( displayName );
00908   if ( realDisplayName.isEmpty() && comment.isEmpty() ) {
00909     return addrSpec;
00910   } else if ( comment.isEmpty() ) {
00911     if ( !realDisplayName.startsWith( '\"' ) ) {
00912       return quoteNameIfNecessary( realDisplayName ) + " <" + addrSpec + '>';
00913     } else {
00914       return realDisplayName + " <" + addrSpec + '>';
00915     }
00916   } else if ( realDisplayName.isEmpty() ) {
00917     QString commentStr = comment;
00918     return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + '>';
00919   } else {
00920     return realDisplayName + " (" + comment + ") <" + addrSpec + '>';
00921   }
00922 }
00923 
00924 //-----------------------------------------------------------------------------
00925 QString KPIMUtils::fromIdn( const QString &addrSpec )
00926 {
00927   const int atPos = addrSpec.lastIndexOf( '@' );
00928   if ( atPos == -1 ) {
00929     return addrSpec;
00930   }
00931 
00932   QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() );
00933   if ( idn.isEmpty() ) {
00934     return QString();
00935   }
00936 
00937   return addrSpec.left( atPos + 1 ) + idn;
00938 }
00939 
00940 //-----------------------------------------------------------------------------
00941 QString KPIMUtils::toIdn( const QString &addrSpec )
00942 {
00943   const int atPos = addrSpec.lastIndexOf( '@' );
00944   if ( atPos == -1 ) {
00945     return addrSpec;
00946   }
00947 
00948   QString idn = KUrl::toAce( addrSpec.mid( atPos + 1 ) );
00949   if ( idn.isEmpty() ) {
00950     return addrSpec;
00951   }
00952 
00953   return addrSpec.left( atPos + 1 ) + idn;
00954 }
00955 
00956 //-----------------------------------------------------------------------------
00957 QString KPIMUtils::normalizeAddressesAndDecodeIdn( const QString &str )
00958 {
00959   //  kDebug() << str;
00960   if ( str.isEmpty() ) {
00961     return str;
00962   }
00963 
00964   const QStringList addressList = splitAddressList( str );
00965   QStringList normalizedAddressList;
00966 
00967   QByteArray displayName, addrSpec, comment;
00968 
00969   for ( QStringList::ConstIterator it = addressList.begin();
00970         ( it != addressList.end() );
00971         ++it ) {
00972     if ( !(*it).isEmpty() ) {
00973       if ( splitAddress( (*it).toUtf8(),
00974                          displayName, addrSpec, comment ) == AddressOk ) {
00975 
00976         displayName = KMime::decodeRFC2047String(displayName).toUtf8();
00977         comment = KMime::decodeRFC2047String(comment).toUtf8();
00978 
00979         normalizedAddressList
00980           << normalizedAddress( QString::fromUtf8( displayName ),
00981                                 fromIdn( QString::fromUtf8( addrSpec ) ),
00982                                 QString::fromUtf8( comment ) );
00983       }
00984     }
00985   }
00986   /*
00987     kDebug() << "normalizedAddressList: \""
00988              << normalizedAddressList.join( ", " )
00989              << "\"";
00990   */
00991   return normalizedAddressList.join( ", " );
00992 }
00993 
00994 //-----------------------------------------------------------------------------
00995 QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str )
00996 {
00997   //kDebug() << str;
00998   if ( str.isEmpty() ) {
00999     return str;
01000   }
01001 
01002   const QStringList addressList = splitAddressList( str );
01003   QStringList normalizedAddressList;
01004 
01005   QByteArray displayName, addrSpec, comment;
01006 
01007   for ( QStringList::ConstIterator it = addressList.begin();
01008         ( it != addressList.end() );
01009         ++it ) {
01010     if ( !(*it).isEmpty() ) {
01011       if ( splitAddress( (*it).toUtf8(),
01012                          displayName, addrSpec, comment ) == AddressOk ) {
01013 
01014         normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ),
01015                                                     toIdn( QString::fromUtf8( addrSpec ) ),
01016                                                     QString::fromUtf8( comment ) );
01017       }
01018     }
01019   }
01020 
01021   /*
01022     kDebug() << "normalizedAddressList: \""
01023              << normalizedAddressList.join( ", " )
01024              << "\"";
01025   */
01026   return normalizedAddressList.join( ", " );
01027 }
01028 
01029 //-----------------------------------------------------------------------------
01030 // Escapes unescaped doublequotes in str.
01031 static QString escapeQuotes( const QString &str )
01032 {
01033   if ( str.isEmpty() ) {
01034     return QString();
01035   }
01036 
01037   QString escaped;
01038   // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" )
01039   escaped.reserve( 2 * str.length() );
01040   unsigned int len = 0;
01041   for ( int i = 0; i < str.length(); ++i, ++len ) {
01042     if ( str[i] == '"' ) { // unescaped doublequote
01043       escaped[len] = '\\';
01044       ++len;
01045     } else if ( str[i] == '\\' ) { // escaped character
01046       escaped[len] = '\\';
01047       ++len;
01048       ++i;
01049       if ( i >= str.length() ) { // handle trailing '\' gracefully
01050         break;
01051       }
01052     }
01053     escaped[len] = str[i];
01054   }
01055   escaped.truncate( len );
01056   return escaped;
01057 }
01058 
01059 //-----------------------------------------------------------------------------
01060 QString KPIMUtils::quoteNameIfNecessary( const QString &str )
01061 {
01062   QString quoted = str;
01063 
01064   QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
01065   // avoid double quoting
01066   if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) {
01067     quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\"";
01068   } else if ( quoted.indexOf( needQuotes ) != -1 ) {
01069     quoted = "\"" + escapeQuotes( quoted ) + "\"";
01070   }
01071 
01072   return quoted;
01073 }
01074 
01075 KUrl KPIMUtils::encodeMailtoUrl( const QString &mailbox )
01076 {
01077   const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox, "utf-8" );
01078   KUrl mailtoUrl;
01079   mailtoUrl.setProtocol( "mailto" );
01080   mailtoUrl.setPath( encodedPath );
01081   return mailtoUrl;
01082 }
01083 
01084 QString KPIMUtils::decodeMailtoUrl( const KUrl &mailtoUrl )
01085 {
01086   Q_ASSERT( mailtoUrl.protocol().toLower() == "mailto" );
01087   return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() );
01088 }

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
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.4
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