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

KDE's Doxygen guidelines are available online.

kpimutils

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

kdepimlibs-4.8.5 API Reference

Skip menu "kdepimlibs-4.8.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal