/* This file is part of the kpimutils library. Copyright (c) 2004 Matt Douhan 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. */ /** @file This file is part of the KDEPIM Utilities library and provides static methods for email address validation. @author Matt Douhan \ */ #include "email.h" #include #include #include #include #include #include #include static const KCatalogLoader loader( QLatin1String("libkpimutils") ); using namespace KPIMUtils; //----------------------------------------------------------------------------- QStringList KPIMUtils::splitAddressList( 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 ( int index = 0; index 0 ) { commentlevel--; } else { 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.simplified(); } 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.simplified(); } } return list; } //----------------------------------------------------------------------------- // Used by KPIMUtils::splitAddress(...) and KPIMUtils::firstEmailAddress(...). KPIMUtils::EmailParseResult splitAddressInternal( const QByteArray address, QByteArray &displayName, QByteArray &addrSpec, QByteArray &comment, bool allowMultipleAddresses ) { // kDebug() << "address"; displayName = ""; addrSpec = ""; comment = ""; if ( address.isEmpty() ) { return 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 ( const 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 UnexpectedEnd; } break; case ',' : if ( !inQuotedString ) { if ( allowMultipleAddresses ) { stop = true; } else { return 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 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 UnexpectedEnd; } break; default : addrSpec += *p; } break; } } // switch ( context ) } // check for errors if ( inQuotedString ) { return UnbalancedQuote; } if ( context == InComment ) { return UnbalancedParens; } if ( context == InAngleAddress ) { return UnclosedAngleAddr; } displayName = displayName.trimmed(); comment = comment.trimmed(); addrSpec = addrSpec.trimmed(); if ( addrSpec.isEmpty() ) { if ( displayName.isEmpty() ) { return NoAddressSpec; } else { addrSpec = displayName; displayName.truncate( 0 ); } } /* kDebug() << "display-name : \"" << displayName << "\""; kDebug() << "comment : \"" << comment << "\""; kDebug() << "addr-spec : \"" << addrSpec << "\""; */ return AddressOk; } //----------------------------------------------------------------------------- EmailParseResult KPIMUtils::splitAddress( const QByteArray &address, QByteArray &displayName, QByteArray &addrSpec, QByteArray &comment ) { return splitAddressInternal( address, displayName, addrSpec, comment, false/* don't allow multiple addresses */ ); } //----------------------------------------------------------------------------- EmailParseResult KPIMUtils::splitAddress( const QString &address, QString &displayName, QString &addrSpec, QString &comment ) { QByteArray d, a, c; // FIXME: toUtf8() is probably not safe here, what if the second byte of a multi-byte character // has the same code as one of the ASCII characters that splitAddress uses as delimiters? EmailParseResult result = splitAddress( address.toUtf8(), d, a, c ); if ( result == AddressOk ) { displayName = QString::fromUtf8( d ); addrSpec = QString::fromUtf8( a ); comment = QString::fromUtf8( c ); } return result; } //----------------------------------------------------------------------------- EmailParseResult KPIMUtils::isValidAddress( 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 judgment further down in the parser bool tooManyAtsFlag = false; int atCount = aStr.count( QLatin1Char('@') ); if ( atCount > 1 ) { tooManyAtsFlag = true; } else if ( atCount == 0 ) { return TooFewAts; } int dotCount = aStr.count( QLatin1Char('.')); // 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].toLatin1() ) { 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 ',' : 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; case '.' : if ( inQuotedString ) { --dotCount; } break; } break; } case InComment : { switch ( aStr[index].toLatin1() ) { 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].toLatin1() ) { case ',' : if ( !inQuotedString ) { return UnexpectedComma; } break; case '"' : inQuotedString = !inQuotedString; break; case '@' : if ( inQuotedString ) { --atCount; } if ( atCount == 1 ) { tooManyAtsFlag = false; } break; case '.' : if ( inQuotedString ) { --dotCount; } break; case '>' : if ( !inQuotedString ) { context = TopLevel; break; } break; case '\\' : // quoted character ++index; // skip the '\' if ( ( index + 1 ) > strlen ) { return UnexpectedEnd; } break; } break; } } } if ( dotCount == 0 && !inQuotedString ) { return TooFewDots; } 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; } //----------------------------------------------------------------------------- KPIMUtils::EmailParseResult KPIMUtils::isValidAddressList( const QString &aStr, QString &badAddr ) { if ( aStr.isEmpty() ) { return AddressEmpty; } const QStringList list = splitAddressList( aStr ); QStringList::const_iterator it = list.begin(); EmailParseResult errorCode = AddressOk; for ( it = list.begin(); it != list.end(); ++it ) { qDebug()<<" *it"<<(*it); errorCode = isValidAddress( *it ); if ( errorCode != AddressOk ) { badAddr = ( *it ); break; } } return errorCode; } //----------------------------------------------------------------------------- QString KPIMUtils::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 angle bracket." ); case UnopenedAngleAddr : return i18n( "The email address you entered is not valid because it " "contains too many closing angle brackets." ); 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 a '\\' 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@example.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 display name." ); case TooFewDots : 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." ); } return i18n( "Unknown problem with email address" ); } //----------------------------------------------------------------------------- bool KPIMUtils::isValidSimpleAddress( 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.lastIndexOf( QLatin1Char('@') ); QString domainPart = aStr.mid( atChar + 1 ); QString localPart = aStr.left( atChar ); // Both of these parts must be non empty // after all we cannot have emails like: // @kde.org, or foo@ if ( localPart.isEmpty() || domainPart.isEmpty() ) { return false; } bool tooManyAtsFlag = false; bool inQuotedString = false; int atCount = localPart.count( QLatin1Char('@') ); unsigned int strlen = localPart.length(); for ( unsigned int index = 0; index < strlen; index++ ) { switch ( localPart[ index ].toLatin1() ) { case '"' : inQuotedString = !inQuotedString; break; case '@' : if ( inQuotedString ) { --atCount; if ( atCount == 0 ) { tooManyAtsFlag = false; } } break; } } QString addrRx; if ( localPart[ 0 ] == QLatin1Char('\"') || localPart[ localPart.length()-1 ] == QLatin1Char('\"') ) { addrRx = QLatin1String("\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@"); } else { addrRx = QLatin1String("[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@"); } if ( domainPart[ 0 ] == QLatin1Char('[') || domainPart[ domainPart.length()-1 ] == QLatin1Char(']') ) { addrRx += QLatin1String("\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]"); } else { addrRx += QLatin1String("[\\w-#]+(\\.[\\w-#]+)*"); } QRegExp rx( addrRx ); return rx.exactMatch( aStr ) && !tooManyAtsFlag; } //----------------------------------------------------------------------------- QString KPIMUtils::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@example.org." ); } //----------------------------------------------------------------------------- QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address ) { QByteArray dummy1, dummy2, addrSpec; EmailParseResult result = splitAddressInternal( address, dummy1, addrSpec, dummy2, false/* don't allow multiple addresses */ ); if ( result != AddressOk ) { addrSpec = QByteArray(); if ( result != AddressEmpty ) { kDebug() << "Input:" << address << "\nError:" << emailParseResultToString( result ); } } return addrSpec; } //----------------------------------------------------------------------------- QString KPIMUtils::extractEmailAddress( const QString &address ) { return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) ); } //----------------------------------------------------------------------------- QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses ) { QByteArray dummy1, dummy2, addrSpec; EmailParseResult result = splitAddressInternal( addresses, dummy1, addrSpec, dummy2, true/* allow multiple addresses */ ); if ( result != AddressOk ) { addrSpec = QByteArray(); if ( result != AddressEmpty ) { kDebug() << "Input: aStr\nError:" << emailParseResultToString( result ); } } return addrSpec; } //----------------------------------------------------------------------------- QString KPIMUtils::firstEmailAddress( const QString &addresses ) { return QString::fromUtf8( firstEmailAddress( addresses.toUtf8() ) ); } //----------------------------------------------------------------------------- bool KPIMUtils::extractEmailAddressAndName( const QString &aStr, QString &mail, QString &name ) { name.clear(); mail.clear(); 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 ( QLatin1Char('(') == c ) { commentstack++; } if ( QLatin1Char(')') == c ) { commentstack--; } bInComment = commentstack != 0; if ( QLatin1Char('"') == c && !bInComment ) { bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail; } if ( !bInComment && !bInQuotesOutsideOfEmail ) { if ( QLatin1Char('@') == 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 ( QLatin1Char('<') != c ) { name.append( c ); } else { break; } } mail = aStr.mid( i + 1 ); if ( mail.endsWith( QLatin1Char('>') ) ) { 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 ( QLatin1Char('(') == c ) { if ( !name.isEmpty() ) { name.prepend( QLatin1Char(' ') ); } bInComment = false; } else { name.prepend( c ); // all comment stuff is part of the name } } else if ( bInQuotesOutsideOfEmail ) { if ( QLatin1Char(cQuotes) == c ) { bInQuotesOutsideOfEmail = false; } else if ( c != QLatin1Char('\\') ) { name.prepend( c ); } } else { // found the start of this addressee ? if ( QLatin1Char(',') == c ) { break; } // stuff is before the leading '<' ? if ( iMailStart ) { if ( QLatin1Char(cQuotes) == c ) { bInQuotesOutsideOfEmail = true; // end of quoted text found } else { name.prepend( c ); } } else { switch ( c.toLatin1() ) { case '<': iMailStart = i; break; case ')': if ( !name.isEmpty() ) { name.prepend( QLatin1Char(' ') ); } bInComment = true; break; default: if ( QLatin1Char(' ') != c ) { mail.prepend( c ); } } } } } name = name.simplified(); mail = mail.simplified(); if ( mail.isEmpty() ) { return false; } mail.append( QLatin1Char('@') ); // 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 ( QLatin1Char(')') == c ) { if ( --parenthesesNesting == 0 ) { bInComment = false; if ( !name.isEmpty() ) { name.append( QLatin1Char(' ') ); } } else { // nested ")", add it name.append( QLatin1Char(')') ); // name can't be empty here } } else { if ( QLatin1Char('(') == c ) { // nested "(" ++parenthesesNesting; } name.append( c ); // all comment stuff is part of the name } } else if ( bInQuotesOutsideOfEmail ) { if ( QLatin1Char(cQuotes) == c ) { bInQuotesOutsideOfEmail = false; } else if ( c != QLatin1Char('\\') ) { name.append( c ); } } else { // found the end of this addressee ? if ( QLatin1Char(',') == c ) { break; } // stuff is behind the trailing '>' ? if ( iMailEnd ) { if ( QLatin1Char(cQuotes) == c ) { bInQuotesOutsideOfEmail = true; // start of quoted text found } else { name.append( c ); } } else { switch ( c.toLatin1() ) { case '>': iMailEnd = i; break; case '(': if ( !name.isEmpty() ) { name.append( QLatin1Char(' ') ); } if ( ++parenthesesNesting > 0 ) { bInComment = true; } break; default: if ( QLatin1Char(' ') != c ) { mail.append( c ); } } } } } } name = name.simplified(); mail = mail.simplified(); return ! ( name.isEmpty() || mail.isEmpty() ); } //----------------------------------------------------------------------------- bool KPIMUtils::compareEmail( const QString &email1, const QString &email2, bool matchName ) { QString e1Name, e1Email, e2Name, e2Email; extractEmailAddressAndName( email1, e1Email, e1Name ); extractEmailAddressAndName( email2, e2Email, e2Name ); return e1Email == e2Email && ( !matchName || ( e1Name == e2Name ) ); } //----------------------------------------------------------------------------- QString KPIMUtils::normalizedAddress( const QString &displayName, const QString &addrSpec, const QString &comment ) { const QString realDisplayName = KMime::removeBidiControlChars( displayName ); if ( realDisplayName.isEmpty() && comment.isEmpty() ) { return addrSpec; } else if ( comment.isEmpty() ) { if ( !realDisplayName.startsWith( QLatin1Char('\"') ) ) { return quoteNameIfNecessary( realDisplayName ) + QLatin1String(" <") + addrSpec + QLatin1Char('>'); } else { return realDisplayName + QLatin1String(" <") + addrSpec + QLatin1Char('>'); } } else if ( realDisplayName.isEmpty() ) { QString commentStr = comment; return quoteNameIfNecessary( commentStr ) + QLatin1String(" <") + addrSpec + QLatin1Char('>'); } else { return realDisplayName + QLatin1String(" (") + comment +QLatin1String( ") <") + addrSpec + QLatin1Char('>'); } } //----------------------------------------------------------------------------- QString KPIMUtils::fromIdn( const QString &addrSpec ) { const int atPos = addrSpec.lastIndexOf( QLatin1Char('@') ); if ( atPos == -1 ) { return addrSpec; } QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() ); if ( idn.isEmpty() ) { return QString(); } return addrSpec.left( atPos + 1 ) + idn; } //----------------------------------------------------------------------------- QString KPIMUtils::toIdn( const QString &addrSpec ) { const int atPos = addrSpec.lastIndexOf( QLatin1Char('@') ); if ( atPos == -1 ) { return addrSpec; } QString idn = QLatin1String(KUrl::toAce( addrSpec.mid( atPos + 1 )) ); if ( idn.isEmpty() ) { return addrSpec; } return addrSpec.left( atPos + 1 ) + idn; } //----------------------------------------------------------------------------- QString KPIMUtils::normalizeAddressesAndDecodeIdn( const QString &str ) { // kDebug() << str; if ( str.isEmpty() ) { return str; } const QStringList addressList = splitAddressList( str ); QStringList normalizedAddressList; QByteArray displayName, addrSpec, comment; for ( QStringList::ConstIterator it = addressList.begin(); ( it != addressList.end() ); ++it ) { if ( !( *it ).isEmpty() ) { if ( splitAddress( ( *it ).toUtf8(), displayName, addrSpec, comment ) == AddressOk ) { displayName = KMime::decodeRFC2047String( displayName ).toUtf8(); comment = KMime::decodeRFC2047String( comment ).toUtf8(); normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ), fromIdn( QString::fromUtf8( addrSpec ) ), QString::fromUtf8( comment ) ); } } } /* kDebug() << "normalizedAddressList: \"" << normalizedAddressList.join( ", " ) << "\""; */ return normalizedAddressList.join( QLatin1String(", ") ); } //----------------------------------------------------------------------------- QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str ) { //kDebug() << str; if ( str.isEmpty() ) { return str; } const QStringList addressList = splitAddressList( str ); QStringList normalizedAddressList; QByteArray displayName, addrSpec, comment; for ( QStringList::ConstIterator it = addressList.begin(); ( it != addressList.end() ); ++it ) { if ( !( *it ).isEmpty() ) { if ( splitAddress( ( *it ).toUtf8(), displayName, addrSpec, comment ) == AddressOk ) { normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ), toIdn( QString::fromUtf8( addrSpec ) ), QString::fromUtf8( comment ) ); } } } /* kDebug() << "normalizedAddressList: \"" << normalizedAddressList.join( ", " ) << "\""; */ return normalizedAddressList.join( QLatin1String(", ") ); } //----------------------------------------------------------------------------- // 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 ( int i = 0; i < str.length(); ++i, ++len ) { if ( str[i] == QLatin1Char('"') ) { // unescaped doublequote escaped[len] = QLatin1Char('\\'); ++len; } else if ( str[i] == QLatin1Char('\\') ) { // escaped character escaped[len] = QLatin1Char('\\'); ++len; ++i; if ( i >= str.length() ) { // handle trailing '\' gracefully break; } } escaped[len] = str[i]; } escaped.truncate( len ); return escaped; } //----------------------------------------------------------------------------- QString KPIMUtils::quoteNameIfNecessary( const QString &str ) { QString quoted = str; QRegExp needQuotes( QLatin1String("[^ 0-9A-Za-z\\x0080-\\xFFFF]") ); // avoid double quoting if ( ( quoted[0] == QLatin1Char('"') ) && ( quoted[quoted.length() - 1] ==QLatin1Char( '"') ) ) { quoted = QLatin1String("\"") + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + QLatin1String("\""); } else if ( quoted.indexOf( needQuotes ) != -1 ) { quoted = QLatin1String("\"") + escapeQuotes( quoted ) + QLatin1String("\""); } return quoted; } KUrl KPIMUtils::encodeMailtoUrl( const QString &mailbox ) { const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox, "utf-8" ); KUrl mailtoUrl; mailtoUrl.setProtocol( QLatin1String("mailto") ); mailtoUrl.setPath( QLatin1String(encodedPath) ); return mailtoUrl; } QString KPIMUtils::decodeMailtoUrl( const KUrl &mailtoUrl ) { Q_ASSERT( mailtoUrl.protocol().toLower() == QLatin1String("mailto") ); return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() ); }