diff options
Diffstat (limited to 'libemailfunctions/email.cpp')
-rw-r--r-- | libemailfunctions/email.cpp | 973 |
1 files changed, 973 insertions, 0 deletions
diff --git a/libemailfunctions/email.cpp b/libemailfunctions/email.cpp new file mode 100644 index 000000000..acbfa2679 --- /dev/null +++ b/libemailfunctions/email.cpp @@ -0,0 +1,973 @@ +/* -*- mode: C++; c-file-style: "gnu" -*- + + This file is part of kdepim. + Copyright (c) 2004 KDEPIM developers + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "email.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kidna.h> +#include <kmime_util.h> + +#include <qregexp.h> + +//----------------------------------------------------------------------------- +QStringList KPIM::splitEmailAddrList(const QString& aStr) +{ + // Features: + // - always ignores quoted characters + // - ignores everything (including parentheses and commas) + // inside quoted strings + // - supports nested comments + // - ignores everything (including double quotes and commas) + // inside comments + + QStringList list; + + if (aStr.isEmpty()) + return list; + + QString addr; + uint addrstart = 0; + int commentlevel = 0; + bool insidequote = false; + + for (uint index=0; index<aStr.length(); index++) { + // the following conversion to latin1 is o.k. because + // we can safely ignore all non-latin1 characters + switch (aStr[index].latin1()) { + case '"' : // start or end of quoted string + if (commentlevel == 0) + insidequote = !insidequote; + break; + case '(' : // start of comment + if (!insidequote) + commentlevel++; + break; + case ')' : // end of comment + if (!insidequote) { + if (commentlevel > 0) + commentlevel--; + else { + kdDebug(5300) << "Error in address splitting: Unmatched ')'" + << endl; + return list; + } + } + break; + case '\\' : // quoted character + index++; // ignore the quoted character + break; + case ',' : + case ';' : + if (!insidequote && (commentlevel == 0)) { + addr = aStr.mid(addrstart, index-addrstart); + if (!addr.isEmpty()) + list += addr.simplifyWhiteSpace(); + addrstart = index+1; + } + break; + } + } + // append the last address to the list + if (!insidequote && (commentlevel == 0)) { + addr = aStr.mid(addrstart, aStr.length()-addrstart); + if (!addr.isEmpty()) + list += addr.simplifyWhiteSpace(); + } + else + kdDebug(5300) << "Error in address splitting: " + << "Unexpected end of address list" + << endl; + + return list; +} + +//----------------------------------------------------------------------------- +// Used by KPIM::splitAddress(...) and KPIM::getFirstEmailAddress(...). +KPIM::EmailParseResult splitAddressInternal( const QCString& address, + QCString & displayName, + QCString & addrSpec, + QCString & comment, + bool allowMultipleAddresses ) +{ +// kdDebug() << "KMMessage::splitAddress( " << address << " )" << endl; + + displayName = ""; + addrSpec = ""; + comment = ""; + + if ( address.isEmpty() ) + return KPIM::AddressEmpty; + + // The following is a primitive parser for a mailbox-list (cf. RFC 2822). + // The purpose is to extract a displayable string from the mailboxes. + // Comments in the addr-spec are not handled. No error checking is done. + + enum { TopLevel, InComment, InAngleAddress } context = TopLevel; + bool inQuotedString = false; + int commentLevel = 0; + bool stop = false; + + for ( char* p = address.data(); *p && !stop; ++p ) { + switch ( context ) { + case TopLevel : { + switch ( *p ) { + case '"' : inQuotedString = !inQuotedString; + displayName += *p; + break; + case '(' : if ( !inQuotedString ) { + context = InComment; + commentLevel = 1; + } + else + displayName += *p; + break; + case '<' : if ( !inQuotedString ) { + context = InAngleAddress; + } + else + displayName += *p; + break; + case '\\' : // quoted character + displayName += *p; + ++p; // skip the '\' + if ( *p ) + displayName += *p; + else + return KPIM::UnexpectedEnd; + break; + case ',' : + case ';' : if ( !inQuotedString ) { + if ( allowMultipleAddresses ) + stop = true; + else + return KPIM::UnexpectedComma; + } + else + displayName += *p; + break; + default : displayName += *p; + } + break; + } + case InComment : { + switch ( *p ) { + case '(' : ++commentLevel; + comment += *p; + break; + case ')' : --commentLevel; + if ( commentLevel == 0 ) { + context = TopLevel; + comment += ' '; // separate the text of several comments + } + else + comment += *p; + break; + case '\\' : // quoted character + comment += *p; + ++p; // skip the '\' + if ( *p ) + comment += *p; + else + return KPIM::UnexpectedEnd; + break; + default : comment += *p; + } + break; + } + case InAngleAddress : { + switch ( *p ) { + case '"' : inQuotedString = !inQuotedString; + addrSpec += *p; + break; + case '>' : if ( !inQuotedString ) { + context = TopLevel; + } + else + addrSpec += *p; + break; + case '\\' : // quoted character + addrSpec += *p; + ++p; // skip the '\' + if ( *p ) + addrSpec += *p; + else + return KPIM::UnexpectedEnd; + break; + default : addrSpec += *p; + } + break; + } + } // switch ( context ) + } + // check for errors + if ( inQuotedString ) + return KPIM::UnbalancedQuote; + if ( context == InComment ) + return KPIM::UnbalancedParens; + if ( context == InAngleAddress ) + return KPIM::UnclosedAngleAddr; + + displayName = displayName.stripWhiteSpace(); + comment = comment.stripWhiteSpace(); + addrSpec = addrSpec.stripWhiteSpace(); + + if ( addrSpec.isEmpty() ) { + if ( displayName.isEmpty() ) + return KPIM::NoAddressSpec; + else { + addrSpec = displayName; + displayName.truncate( 0 ); + } + } +/* + kdDebug() << "display-name : \"" << displayName << "\"" << endl; + kdDebug() << "comment : \"" << comment << "\"" << endl; + kdDebug() << "addr-spec : \"" << addrSpec << "\"" << endl; +*/ + return KPIM::AddressOk; +} + + +//----------------------------------------------------------------------------- +KPIM::EmailParseResult KPIM::splitAddress( const QCString& address, + QCString & displayName, + QCString & addrSpec, + QCString & comment ) +{ + return splitAddressInternal( address, displayName, addrSpec, comment, + false /* don't allow multiple addresses */ ); +} + + +//----------------------------------------------------------------------------- +KPIM::EmailParseResult KPIM::splitAddress( const QString & address, + QString & displayName, + QString & addrSpec, + QString & comment ) +{ + QCString d, a, c; + KPIM::EmailParseResult result = splitAddress( address.utf8(), d, a, c ); + if ( result == AddressOk ) { + displayName = QString::fromUtf8( d ); + addrSpec = QString::fromUtf8( a ); + comment = QString::fromUtf8( c ); + } + return result; +} + + +//----------------------------------------------------------------------------- +KPIM::EmailParseResult KPIM::isValidEmailAddress( const QString& aStr ) +{ + // If we are passed an empty string bail right away no need to process further + // and waste resources + if ( aStr.isEmpty() ) { + return AddressEmpty; + } + + // count how many @'s are in the string that is passed to us + // if 0 or > 1 take action + // at this point to many @'s cannot bail out right away since + // @ is allowed in qoutes, so we use a bool to keep track + // and then make a judgement further down in the parser + // FIXME count only @ not in double quotes + + bool tooManyAtsFlag = false; + + int atCount = aStr.contains('@'); + if ( atCount > 1 ) { + tooManyAtsFlag = true;; + } else if ( atCount == 0 ) { + return TooFewAts; + } + + // The main parser, try and catch all weird and wonderful + // mistakes users and/or machines can create + + enum { TopLevel, InComment, InAngleAddress } context = TopLevel; + bool inQuotedString = false; + int commentLevel = 0; + + unsigned int strlen = aStr.length(); + + for ( unsigned int index=0; index < strlen; index++ ) { + switch ( context ) { + case TopLevel : { + switch ( aStr[index].latin1() ) { + case '"' : inQuotedString = !inQuotedString; + break; + case '(' : + if ( !inQuotedString ) { + context = InComment; + commentLevel = 1; + } + break; + case '[' : + if ( !inQuotedString ) { + return InvalidDisplayName; + } + break; + case ']' : + if ( !inQuotedString ) { + return InvalidDisplayName; + } + break; + case ':' : + if ( !inQuotedString ) { + return DisallowedChar; + } + break; + case '<' : + if ( !inQuotedString ) { + context = InAngleAddress; + } + break; + case '\\' : // quoted character + ++index; // skip the '\' + if (( index + 1 )> strlen ) { + return UnexpectedEnd; + } + break; + case ',' : + case ';' : + if ( !inQuotedString ) + return UnexpectedComma; + break; + case ')' : + if ( !inQuotedString ) + return UnbalancedParens; + break; + case '>' : + if ( !inQuotedString ) + return UnopenedAngleAddr; + break; + case '@' : + if ( !inQuotedString ) { + if ( index == 0 ) { // Missing local part + return MissingLocalPart; + } else if( index == strlen-1 ) { + return MissingDomainPart; + } + } else if ( inQuotedString ) { + --atCount; + if ( atCount == 1 ) { + tooManyAtsFlag = false; + } + } + break; + } + break; + } + case InComment : { + switch ( aStr[index] ) { + case '(' : ++commentLevel; + break; + case ')' : --commentLevel; + if ( commentLevel == 0 ) { + context = TopLevel; + } + break; + case '\\' : // quoted character + ++index; // skip the '\' + if (( index + 1 )> strlen ) { + return UnexpectedEnd; + } + break; + } + break; + } + + case InAngleAddress : { + switch ( aStr[index] ) { + case ',' : + case ';' : + if ( !inQuotedString ) { + return UnexpectedComma; + } + break; + case '"' : inQuotedString = !inQuotedString; + break; + case '@' : + if ( inQuotedString ) { + --atCount; + if ( atCount == 1 ) { + tooManyAtsFlag = false; + } + } + break; + case '>' : + if ( !inQuotedString ) { + context = TopLevel; + break; + } + break; + case '\\' : // quoted character + ++index; // skip the '\' + if (( index + 1 )> strlen ) { + return UnexpectedEnd; + } + break; + } + break; + } + } + } + + if ( atCount == 0 && !inQuotedString ) + return TooFewAts; + + if ( inQuotedString ) + return UnbalancedQuote; + + if ( context == InComment ) + return UnbalancedParens; + + if ( context == InAngleAddress ) + return UnclosedAngleAddr; + + if ( tooManyAtsFlag ) { + return TooManyAts; + } + return AddressOk; +} + +//----------------------------------------------------------------------------- +QString KPIM::emailParseResultToString( EmailParseResult errorCode ) +{ + switch ( errorCode ) { + case TooManyAts : + return i18n("The email address you entered is not valid because it " + "contains more than one @. " + "You will not create valid messages if you do not " + "change your address."); + case TooFewAts : + return i18n("The email address you entered is not valid because it " + "does not contain a @." + "You will not create valid messages if you do not " + "change your address."); + case AddressEmpty : + return i18n("You have to enter something in the email address field."); + case MissingLocalPart : + return i18n("The email address you entered is not valid because it " + "does not contain a local part."); + case MissingDomainPart : + return i18n("The email address you entered is not valid because it " + "does not contain a domain part."); + case UnbalancedParens : + return i18n("The email address you entered is not valid because it " + "contains unclosed comments/brackets."); + case AddressOk : + return i18n("The email address you entered is valid."); + case UnclosedAngleAddr : + return i18n("The email address you entered is not valid because it " + "contains an unclosed anglebracket."); + case UnopenedAngleAddr : + return i18n("The email address you entered is not valid because it " + "contains an unopened anglebracket."); + case UnexpectedComma : + return i18n("The email address you have entered is not valid because it " + "contains an unexpected comma."); + case UnexpectedEnd : + return i18n("The email address you entered is not valid because it ended " + "unexpectedly, this probably means you have used an escaping type " + "character like an \\ as the last character in your email " + "address."); + case UnbalancedQuote : + return i18n("The email address you entered is not valid because it " + "contains quoted text which does not end."); + case NoAddressSpec : + return i18n("The email address you entered is not valid because it " + "does not seem to contain an actual email address, i.e. " + "something of the form joe@kde.org."); + case DisallowedChar : + return i18n("The email address you entered is not valid because it " + "contains an illegal character."); + case InvalidDisplayName : + return i18n("The email address you have entered is not valid because it " + "contains an invalid displayname."); + } + return i18n("Unknown problem with email address"); +} + +//----------------------------------------------------------------------------- +bool KPIM::isValidSimpleEmailAddress( const QString& aStr ) +{ + // If we are passed an empty string bail right away no need to process further + // and waste resources + if ( aStr.isEmpty() ) { + return false; + } + + int atChar = aStr.findRev( '@' ); + QString domainPart = aStr.mid( atChar + 1); + QString localPart = aStr.left( atChar ); + bool tooManyAtsFlag = false; + bool inQuotedString = false; + int atCount = localPart.contains( '@' ); + + unsigned int strlen = localPart.length(); + for ( unsigned int index=0; index < strlen; index++ ) { + switch( localPart[ index ].latin1() ) { + case '"' : inQuotedString = !inQuotedString; + break; + case '@' : + if ( inQuotedString ) { + --atCount; + if ( atCount == 0 ) { + tooManyAtsFlag = false; + } + } + break; + } + } + + QString addrRx = "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@"; + if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) { + addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@"; + } + if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) { + addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]"; + } else { + addrRx += "[\\w-]+(\\.[\\w-]+)*"; + } + QRegExp rx( addrRx ); + return rx.exactMatch( aStr ) && !tooManyAtsFlag; +} + +//----------------------------------------------------------------------------- +QString KPIM::simpleEmailAddressErrorMsg() +{ + return i18n("The email address you entered is not valid because it " + "does not seem to contain an actual email address, i.e. " + "something of the form joe@kde.org."); +} +//----------------------------------------------------------------------------- +QCString KPIM::getEmailAddress( const QCString & address ) +{ + QCString dummy1, dummy2, addrSpec; + KPIM::EmailParseResult result = + splitAddressInternal( address, dummy1, addrSpec, dummy2, + false /* don't allow multiple addresses */ ); + if ( result != AddressOk ) { + addrSpec = QCString(); + kdDebug() // << k_funcinfo << "\n" + << "Input: aStr\nError:" + << emailParseResultToString( result ) << endl; + } + + return addrSpec; +} + + +//----------------------------------------------------------------------------- +QString KPIM::getEmailAddress( const QString & address ) +{ + return QString::fromUtf8( getEmailAddress( address.utf8() ) ); +} + + +//----------------------------------------------------------------------------- +QCString KPIM::getFirstEmailAddress( const QCString & addresses ) +{ + QCString dummy1, dummy2, addrSpec; + KPIM::EmailParseResult result = + splitAddressInternal( addresses, dummy1, addrSpec, dummy2, + true /* allow multiple addresses */ ); + if ( result != AddressOk ) { + addrSpec = QCString(); + kdDebug() // << k_funcinfo << "\n" + << "Input: aStr\nError:" + << emailParseResultToString( result ) << endl; + } + + return addrSpec; +} + + +//----------------------------------------------------------------------------- +QString KPIM::getFirstEmailAddress( const QString & addresses ) +{ + return QString::fromUtf8( getFirstEmailAddress( addresses.utf8() ) ); +} + + +//----------------------------------------------------------------------------- +bool KPIM::getNameAndMail(const QString& aStr, QString& name, QString& mail) +{ + name = QString::null; + mail = QString::null; + + const int len=aStr.length(); + const char cQuotes = '"'; + + bool bInComment = false; + bool bInQuotesOutsideOfEmail = false; + int i=0, iAd=0, iMailStart=0, iMailEnd=0; + QChar c; + unsigned int commentstack = 0; + + // Find the '@' of the email address + // skipping all '@' inside "(...)" comments: + while( i < len ){ + c = aStr[i]; + if( '(' == c ) commentstack++; + if( ')' == c ) commentstack--; + bInComment = commentstack != 0; + if( '"' == c && !bInComment ) + bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail; + + if( !bInComment && !bInQuotesOutsideOfEmail ){ + if( '@' == c ){ + iAd = i; + break; // found it + } + } + ++i; + } + + if ( !iAd ) { + // We suppose the user is typing the string manually and just + // has not finished typing the mail address part. + // So we take everything that's left of the '<' as name and the rest as mail + for( i = 0; len > i; ++i ) { + c = aStr[i]; + if( '<' != c ) + name.append( c ); + else + break; + } + mail = aStr.mid( i+1 ); + if ( mail.endsWith( ">" ) ) + mail.truncate( mail.length() - 1 ); + + } else { + // Loop backwards until we find the start of the string + // or a ',' that is outside of a comment + // and outside of quoted text before the leading '<'. + bInComment = false; + bInQuotesOutsideOfEmail = false; + for( i = iAd-1; 0 <= i; --i ) { + c = aStr[i]; + if( bInComment ) { + if( '(' == c ) { + if( !name.isEmpty() ) + name.prepend( ' ' ); + bInComment = false; + } else { + name.prepend( c ); // all comment stuff is part of the name + } + }else if( bInQuotesOutsideOfEmail ){ + if( cQuotes == c ) + bInQuotesOutsideOfEmail = false; + else + name.prepend( c ); + }else{ + // found the start of this addressee ? + if( ',' == c ) + break; + // stuff is before the leading '<' ? + if( iMailStart ){ + if( cQuotes == c ) + bInQuotesOutsideOfEmail = true; // end of quoted text found + else + name.prepend( c ); + }else{ + switch( c ){ + case '<': + iMailStart = i; + break; + case ')': + if( !name.isEmpty() ) + name.prepend( ' ' ); + bInComment = true; + break; + default: + if( ' ' != c ) + mail.prepend( c ); + } + } + } + } + + name = name.simplifyWhiteSpace(); + mail = mail.simplifyWhiteSpace(); + + if( mail.isEmpty() ) + return false; + + mail.append('@'); + + // Loop forward until we find the end of the string + // or a ',' that is outside of a comment + // and outside of quoted text behind the trailing '>'. + bInComment = false; + bInQuotesOutsideOfEmail = false; + int parenthesesNesting = 0; + for( i = iAd+1; len > i; ++i ) { + c = aStr[i]; + if( bInComment ){ + if( ')' == c ){ + if ( --parenthesesNesting == 0 ) { + bInComment = false; + if( !name.isEmpty() ) + name.append( ' ' ); + } else { + // nested ")", add it + name.append( ')' ); // name can't be empty here + } + } else { + if( '(' == c ) { + // nested "(" + ++parenthesesNesting; + } + name.append( c ); // all comment stuff is part of the name + } + }else if( bInQuotesOutsideOfEmail ){ + if( cQuotes == c ) + bInQuotesOutsideOfEmail = false; + else + name.append( c ); + }else{ + // found the end of this addressee ? + if( ',' == c ) + break; + // stuff is behind the trailing '>' ? + if( iMailEnd ){ + if( cQuotes == c ) + bInQuotesOutsideOfEmail = true; // start of quoted text found + else + name.append( c ); + }else{ + switch( c ){ + case '>': + iMailEnd = i; + break; + case '(': + if( !name.isEmpty() ) + name.append( ' ' ); + if ( ++parenthesesNesting > 0 ) + bInComment = true; + break; + default: + if( ' ' != c ) + mail.append( c ); + } + } + } + } + } + + name = name.simplifyWhiteSpace(); + mail = mail.simplifyWhiteSpace(); + + return ! (name.isEmpty() || mail.isEmpty()); +} + + +//----------------------------------------------------------------------------- +bool KPIM::compareEmail( const QString& email1, const QString& email2, + bool matchName ) +{ + QString e1Name, e1Email, e2Name, e2Email; + + getNameAndMail( email1, e1Name, e1Email ); + getNameAndMail( email2, e2Name, e2Email ); + + return e1Email == e2Email && + ( !matchName || ( e1Name == e2Name ) ); +} + + +//----------------------------------------------------------------------------- +QString KPIM::normalizedAddress( const QString & displayName, + const QString & addrSpec, + const QString & comment ) +{ + if ( displayName.isEmpty() && comment.isEmpty() ) + return addrSpec; + else if ( comment.isEmpty() ) + return quoteNameIfNecessary( displayName ) + " <" + addrSpec + ">"; + else if ( displayName.isEmpty() ) { + QString commentStr = comment; + return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + ">"; + } + else + return displayName + " (" + comment + ") <" + addrSpec + ">"; +} + + +//----------------------------------------------------------------------------- +QString KPIM::decodeIDN( const QString & addrSpec ) +{ + const int atPos = addrSpec.findRev( '@' ); + if ( atPos == -1 ) + return addrSpec; + + QString idn = KIDNA::toUnicode( addrSpec.mid( atPos + 1 ) ); + if ( idn.isEmpty() ) + return QString::null; + + return addrSpec.left( atPos + 1 ) + idn; +} + + +//----------------------------------------------------------------------------- +QString KPIM::encodeIDN( const QString & addrSpec ) +{ + const int atPos = addrSpec.findRev( '@' ); + if ( atPos == -1 ) + return addrSpec; + + QString idn = KIDNA::toAscii( addrSpec.mid( atPos + 1 ) ); + if ( idn.isEmpty() ) + return addrSpec; + + return addrSpec.left( atPos + 1 ) + idn; +} + + +//----------------------------------------------------------------------------- +QString KPIM::normalizeAddressesAndDecodeIDNs( const QString & str ) +{ +// kdDebug() << "KPIM::normalizeAddressesAndDecodeIDNs( \"" +// << str << "\" )" << endl; + if( str.isEmpty() ) + return str; + + const QStringList addressList = KPIM::splitEmailAddrList( str ); + QStringList normalizedAddressList; + + QCString displayName, addrSpec, comment; + + for( QStringList::ConstIterator it = addressList.begin(); + ( it != addressList.end() ); + ++it ) { + if( !(*it).isEmpty() ) { + if ( KPIM::splitAddress( (*it).utf8(), displayName, addrSpec, comment ) + == AddressOk ) { + + displayName = KMime::decodeRFC2047String(displayName).utf8(); + comment = KMime::decodeRFC2047String(comment).utf8(); + + normalizedAddressList << + normalizedAddress( QString::fromUtf8( displayName ), + decodeIDN( QString::fromUtf8( addrSpec ) ), + QString::fromUtf8( comment ) ); + } + else { + kdDebug() << "splitting address failed: " << *it << endl; + } + } + } +/* + kdDebug() << "normalizedAddressList: \"" + << normalizedAddressList.join( ", " ) + << "\"" << endl; +*/ + return normalizedAddressList.join( ", " ); +} + +//----------------------------------------------------------------------------- +QString KPIM::normalizeAddressesAndEncodeIDNs( const QString & str ) +{ + //kdDebug() << "KPIM::normalizeAddressesAndEncodeIDNs( \"" + // << str << "\" )" << endl; + if( str.isEmpty() ) + return str; + + const QStringList addressList = KPIM::splitEmailAddrList( str ); + QStringList normalizedAddressList; + + QCString displayName, addrSpec, comment; + + for( QStringList::ConstIterator it = addressList.begin(); + ( it != addressList.end() ); + ++it ) { + if( !(*it).isEmpty() ) { + if ( KPIM::splitAddress( (*it).utf8(), displayName, addrSpec, comment ) + == AddressOk ) { + + normalizedAddressList << + normalizedAddress( QString::fromUtf8( displayName ), + encodeIDN( QString::fromUtf8( addrSpec ) ), + QString::fromUtf8( comment ) ); + } + else { + kdDebug() << "splitting address failed: " << *it << endl; + } + } + } + + /* + kdDebug() << "normalizedAddressList: \"" + << normalizedAddressList.join( ", " ) + << "\"" << endl; + */ + return normalizedAddressList.join( ", " ); +} + + +//----------------------------------------------------------------------------- +// Escapes unescaped doublequotes in str. +static QString escapeQuotes( const QString & str ) +{ + if ( str.isEmpty() ) + return QString(); + + QString escaped; + // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" ) + escaped.reserve( 2*str.length() ); + unsigned int len = 0; + for ( unsigned int i = 0; i < str.length(); ++i, ++len ) { + if ( str[i] == '"' ) { // unescaped doublequote + escaped[len] = '\\'; + ++len; + } + else if ( str[i] == '\\' ) { // escaped character + escaped[len] = '\\'; + ++len; + ++i; + if ( i >= str.length() ) // handle trailing '\' gracefully + break; + } + escaped[len] = str[i]; + } + escaped.truncate( len ); + return escaped; +} + +//----------------------------------------------------------------------------- +QString KPIM::quoteNameIfNecessary( const QString &str ) +{ + QString quoted = str; + + QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ); + // avoid double quoting + if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) { + quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\""; + } + else if ( quoted.find( needQuotes ) != -1 ) { + quoted = "\"" + escapeQuotes( quoted ) + "\""; + } + + return quoted; +} + |