You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/libemailfunctions/email.cpp

982 lines
29 KiB

/* -*- 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 = "";
// these strings are later copied to displayName resp. addrSpec resp. comment
// we don't operate directly on those variables, since as ByteArray deriverates
// they have a miserable performance on operator+
QString dName;
QString aSpec;
QString cmmt;
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;
dName += *p;
break;
case '(' : if ( !inQuotedString ) {
context = InComment;
commentLevel = 1;
}
else
dName += *p;
break;
case '<' : if ( !inQuotedString ) {
context = InAngleAddress;
}
else
dName += *p;
break;
case '\\' : // quoted character
dName += *p;
++p; // skip the '\'
if ( *p )
dName += *p;
else
return KPIM::UnexpectedEnd;
break;
case ',' :
case ';' : if ( !inQuotedString ) {
if ( allowMultipleAddresses )
stop = true;
else
return KPIM::UnexpectedComma;
}
else
dName += *p;
break;
default : dName += *p;
}
break;
}
case InComment : {
switch ( *p ) {
case '(' : ++commentLevel;
cmmt += *p;
break;
case ')' : --commentLevel;
if ( commentLevel == 0 ) {
context = TopLevel;
cmmt += ' '; // separate the text of several comments
}
else
cmmt += *p;
break;
case '\\' : // quoted character
cmmt += *p;
++p; // skip the '\'
if ( *p )
cmmt += *p;
else
return KPIM::UnexpectedEnd;
break;
default : cmmt += *p;
}
break;
}
case InAngleAddress : {
switch ( *p ) {
case '"' : inQuotedString = !inQuotedString;
aSpec += *p;
break;
case '>' : if ( !inQuotedString ) {
context = TopLevel;
}
else
aSpec += *p;
break;
case '\\' : // quoted character
aSpec += *p;
++p; // skip the '\'
if ( *p )
aSpec += *p;
else
return KPIM::UnexpectedEnd;
break;
default : aSpec += *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 = dName.stripWhiteSpace().latin1();
comment = cmmt.stripWhiteSpace().latin1();
addrSpec = aSpec.stripWhiteSpace().latin1();
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;
}