mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
1116 lines
32 KiB
C++
1116 lines
32 KiB
C++
/*
|
|
This file is part of the kpimutils library.
|
|
Copyright (c) 2004 Matt Douhan <matt@fruitsalad.org>
|
|
|
|
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 \<matt@fruitsalad.org\>
|
|
*/
|
|
#include "email.h"
|
|
|
|
#include <kmime/kmime_util.h>
|
|
|
|
#include <KDebug>
|
|
#include <KLocalizedString>
|
|
#include <KUrl>
|
|
|
|
#include <QtCore/QRegExp>
|
|
#include <QtCore/QByteArray>
|
|
|
|
#include <kglobal.h>
|
|
|
|
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<aStr.length(); index++ ) {
|
|
// the following conversion to latin1 is o.k. because
|
|
// we can safely ignore all non-latin1 characters
|
|
switch ( aStr[index].toLatin1() ) {
|
|
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 {
|
|
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() );
|
|
}
|