29#include <kmime/kmime_util.h>
32#include <KLocalizedString>
35#include <QtCore/QRegExp>
36#include <QtCore/QByteArray>
40static const KCatalogLoader loader( QLatin1String(
"libkpimutils") );
42using namespace KPIMUtils;
57 if ( aStr.isEmpty() ) {
64 bool insidequote =
false;
66 for (
int index = 0; index<aStr.length(); index++ ) {
69 switch ( aStr[index].toLatin1() ) {
71 if ( commentlevel == 0 ) {
72 insidequote = !insidequote;
82 if ( commentlevel > 0 ) {
94 if ( !insidequote && ( commentlevel == 0 ) ) {
95 addr = aStr.mid( addrstart, index - addrstart );
96 if ( !addr.isEmpty() ) {
97 list += addr.simplified();
99 addrstart = index + 1;
105 if ( !insidequote && ( commentlevel == 0 ) ) {
106 addr = aStr.mid( addrstart, aStr.length() - addrstart );
107 if ( !addr.isEmpty() ) {
108 list += addr.simplified();
118 QByteArray &displayName,
119 QByteArray &addrSpec,
121 bool allowMultipleAddresses )
128 if ( address.isEmpty() ) {
140 } context = TopLevel;
141 bool inQuotedString =
false;
142 int commentLevel = 0;
145 for (
const char *p = address.data(); *p && !stop; ++p ) {
151 inQuotedString = !inQuotedString;
155 if ( !inQuotedString ) {
163 if ( !inQuotedString ) {
164 context = InAngleAddress;
179 if ( !inQuotedString ) {
180 if ( allowMultipleAddresses ) {
203 if ( commentLevel == 0 ) {
224 case InAngleAddress :
228 inQuotedString = !inQuotedString;
232 if ( !inQuotedString ) {
255 if ( inQuotedString ) {
258 if ( context == InComment ) {
261 if ( context == InAngleAddress ) {
265 displayName = displayName.trimmed();
266 comment = comment.trimmed();
267 addrSpec = addrSpec.trimmed();
269 if ( addrSpec.isEmpty() ) {
270 if ( displayName.isEmpty() ) {
271 return NoAddressSpec;
273 addrSpec = displayName;
274 displayName.truncate( 0 );
287 QByteArray &displayName,
288 QByteArray &addrSpec,
289 QByteArray &comment )
291 return splitAddressInternal( address, displayName, addrSpec, comment,
297 QString &displayName,
307 displayName = QString::fromUtf8( d );
308 addrSpec = QString::fromUtf8( a );
309 comment = QString::fromUtf8( c );
319 if ( aStr.isEmpty() ) {
329 bool tooManyAtsFlag =
false;
331 int atCount = aStr.count( QLatin1Char(
'@') );
333 tooManyAtsFlag =
true;
334 }
else if ( atCount == 0 ) {
338 int dotCount = aStr.count( QLatin1Char(
'.'));
347 } context = TopLevel;
348 bool inQuotedString =
false;
349 int commentLevel = 0;
351 unsigned int strlen = aStr.length();
353 for (
unsigned int index = 0; index < strlen; index++ ) {
357 switch ( aStr[index].toLatin1() ) {
359 inQuotedString = !inQuotedString;
362 if ( !inQuotedString ) {
368 if ( !inQuotedString ) {
373 if ( !inQuotedString ) {
378 if ( !inQuotedString ) {
383 if ( !inQuotedString ) {
384 context = InAngleAddress;
389 if ( ( index + 1 ) > strlen ) {
394 if ( !inQuotedString ) {
399 if ( !inQuotedString ) {
404 if ( !inQuotedString ) {
409 if ( !inQuotedString ) {
412 }
else if ( index == strlen-1 ) {
415 }
else if ( inQuotedString ) {
417 if ( atCount == 1 ) {
418 tooManyAtsFlag =
false;
423 if ( inQuotedString ) {
432 switch ( aStr[index].toLatin1() ) {
438 if ( commentLevel == 0 ) {
444 if ( ( index + 1 ) > strlen ) {
452 case InAngleAddress :
454 switch ( aStr[index].toLatin1() ) {
456 if ( !inQuotedString ) {
461 inQuotedString = !inQuotedString;
464 if ( inQuotedString ) {
467 if ( atCount == 1 ) {
468 tooManyAtsFlag =
false;
472 if ( inQuotedString ) {
477 if ( !inQuotedString ) {
484 if ( ( index + 1 ) > strlen ) {
494 if ( dotCount == 0 && !inQuotedString ) {
498 if ( atCount == 0 && !inQuotedString ) {
502 if ( inQuotedString ) {
506 if ( context == InComment ) {
510 if ( context == InAngleAddress ) {
514 if ( tooManyAtsFlag ) {
525 if ( aStr.isEmpty() ) {
531 QStringList::const_iterator it = list.begin();
533 for ( it = list.begin(); it != list.end(); ++it ) {
534 qDebug()<<
" *it"<<(*it);
547 switch ( errorCode ) {
549 return i18n(
"The email address you entered is not valid because it "
550 "contains more than one @. "
551 "You will not create valid messages if you do not "
552 "change your address." );
554 return i18n(
"The email address you entered is not valid because it "
555 "does not contain a @. "
556 "You will not create valid messages if you do not "
557 "change your address." );
559 return i18n(
"You have to enter something in the email address field." );
561 return i18n(
"The email address you entered is not valid because it "
562 "does not contain a local part." );
564 return i18n(
"The email address you entered is not valid because it "
565 "does not contain a domain part." );
567 return i18n(
"The email address you entered is not valid because it "
568 "contains unclosed comments/brackets." );
570 return i18n(
"The email address you entered is valid." );
572 return i18n(
"The email address you entered is not valid because it "
573 "contains an unclosed angle bracket." );
575 return i18n(
"The email address you entered is not valid because it "
576 "contains too many closing angle brackets." );
578 return i18n(
"The email address you have entered is not valid because it "
579 "contains an unexpected comma." );
581 return i18n(
"The email address you entered is not valid because it ended "
582 "unexpectedly. This probably means you have used an escaping "
583 "type character like a '\\' as the last character in your "
586 return i18n(
"The email address you entered is not valid because it "
587 "contains quoted text which does not end." );
589 return i18n(
"The email address you entered is not valid because it "
590 "does not seem to contain an actual email address, i.e. "
591 "something of the form joe@example.org." );
593 return i18n(
"The email address you entered is not valid because it "
594 "contains an illegal character." );
596 return i18n(
"The email address you have entered is not valid because it "
597 "contains an invalid display name." );
599 return i18n(
"The email address you entered is not valid because it "
600 "does not contain a \'.\'. "
601 "You will not create valid messages if you do not "
602 "change your address." );
605 return i18n(
"Unknown problem with email address" );
613 if ( aStr.isEmpty() ) {
617 int atChar = aStr.lastIndexOf( QLatin1Char(
'@') );
618 QString domainPart = aStr.mid( atChar + 1 );
619 QString localPart = aStr.left( atChar );
624 if ( localPart.isEmpty() || domainPart.isEmpty() ) {
628 bool tooManyAtsFlag =
false;
629 bool inQuotedString =
false;
630 int atCount = localPart.count( QLatin1Char(
'@') );
632 unsigned int strlen = localPart.length();
633 for (
unsigned int index = 0; index < strlen; index++ ) {
634 switch ( localPart[ index ].toLatin1() ) {
636 inQuotedString = !inQuotedString;
639 if ( inQuotedString ) {
641 if ( atCount == 0 ) {
642 tooManyAtsFlag =
false;
651 if ( localPart[ 0 ] == QLatin1Char(
'\"') || localPart[ localPart.length()-1 ] == QLatin1Char(
'\"') ) {
652 addrRx = QLatin1String(
"\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@");
654 addrRx = QLatin1String(
"[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@");
656 if ( domainPart[ 0 ] == QLatin1Char(
'[') || domainPart[ domainPart.length()-1 ] == QLatin1Char(
']') ) {
657 addrRx += QLatin1String(
"\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]");
659 addrRx += QLatin1String(
"[\\w-#]+(\\.[\\w-#]+)*");
661 QRegExp rx( addrRx );
662 return rx.exactMatch( aStr ) && !tooManyAtsFlag;
668 return i18n(
"The email address you entered is not valid because it "
669 "does not seem to contain an actual email address, i.e. "
670 "something of the form joe@example.org." );
676 QByteArray dummy1, dummy2, addrSpec;
678 splitAddressInternal( address, dummy1, addrSpec, dummy2,
681 addrSpec = QByteArray();
684 <<
"Input:" << address <<
"\nError:"
701 QByteArray dummy1, dummy2, addrSpec;
703 splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
706 addrSpec = QByteArray();
709 <<
"Input: aStr\nError:"
725 QString &mail, QString &name )
730 const int len = aStr.length();
731 const char cQuotes =
'"';
733 bool bInComment =
false;
734 bool bInQuotesOutsideOfEmail =
false;
735 int i = 0, iAd = 0, iMailStart = 0, iMailEnd = 0;
737 unsigned int commentstack = 0;
743 if ( QLatin1Char(
'(') == c ) {
746 if ( QLatin1Char(
')') == c ) {
749 bInComment = commentstack != 0;
750 if ( QLatin1Char(
'"') == c && !bInComment ) {
751 bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
754 if ( !bInComment && !bInQuotesOutsideOfEmail ) {
755 if ( QLatin1Char(
'@') == c ) {
767 for ( i = 0; len > i; ++i ) {
769 if ( QLatin1Char(
'<') != c ) {
775 mail = aStr.mid( i + 1 );
776 if ( mail.endsWith( QLatin1Char(
'>') ) ) {
777 mail.truncate( mail.length() - 1 );
785 bInQuotesOutsideOfEmail =
false;
786 for ( i = iAd-1; 0 <= i; --i ) {
789 if ( QLatin1Char(
'(') == c ) {
790 if ( !name.isEmpty() ) {
791 name.prepend( QLatin1Char(
' ') );
797 }
else if ( bInQuotesOutsideOfEmail ) {
798 if ( QLatin1Char(cQuotes) == c ) {
799 bInQuotesOutsideOfEmail =
false;
800 }
else if ( c != QLatin1Char(
'\\') ) {
805 if ( QLatin1Char(
',') == c ) {
810 if ( QLatin1Char(cQuotes) == c ) {
811 bInQuotesOutsideOfEmail =
true;
816 switch ( c.toLatin1() ) {
821 if ( !name.isEmpty() ) {
822 name.prepend( QLatin1Char(
' ') );
827 if ( QLatin1Char(
' ') != c ) {
835 name = name.simplified();
836 mail = mail.simplified();
838 if ( mail.isEmpty() ) {
842 mail.append( QLatin1Char(
'@') );
848 bInQuotesOutsideOfEmail =
false;
849 int parenthesesNesting = 0;
850 for ( i = iAd+1; len > i; ++i ) {
853 if ( QLatin1Char(
')') == c ) {
854 if ( --parenthesesNesting == 0 ) {
856 if ( !name.isEmpty() ) {
857 name.append( QLatin1Char(
' ') );
861 name.append( QLatin1Char(
')') );
864 if ( QLatin1Char(
'(') == c ) {
866 ++parenthesesNesting;
870 }
else if ( bInQuotesOutsideOfEmail ) {
871 if ( QLatin1Char(cQuotes) == c ) {
872 bInQuotesOutsideOfEmail =
false;
873 }
else if ( c != QLatin1Char(
'\\') ) {
878 if ( QLatin1Char(
',') == c ) {
883 if ( QLatin1Char(cQuotes) == c ) {
884 bInQuotesOutsideOfEmail =
true;
889 switch ( c.toLatin1() ) {
894 if ( !name.isEmpty() ) {
895 name.append( QLatin1Char(
' ') );
897 if ( ++parenthesesNesting > 0 ) {
902 if ( QLatin1Char(
' ') != c ) {
911 name = name.simplified();
912 mail = mail.simplified();
914 return ! ( name.isEmpty() || mail.isEmpty() );
921 QString e1Name, e1Email, e2Name, e2Email;
926 return e1Email == e2Email &&
927 ( !matchName || ( e1Name == e2Name ) );
932 const QString &addrSpec,
933 const QString &comment )
935 const QString realDisplayName = KMime::removeBidiControlChars( displayName );
936 if ( realDisplayName.isEmpty() && comment.isEmpty() ) {
938 }
else if ( comment.isEmpty() ) {
939 if ( !realDisplayName.startsWith( QLatin1Char(
'\"') ) ) {
940 return quoteNameIfNecessary( realDisplayName ) + QLatin1String(
" <") + addrSpec + QLatin1Char(
'>');
942 return realDisplayName + QLatin1String(
" <") + addrSpec + QLatin1Char(
'>');
944 }
else if ( realDisplayName.isEmpty() ) {
945 QString commentStr = comment;
948 return realDisplayName + QLatin1String(
" (") + comment +QLatin1String(
") <") + addrSpec + QLatin1Char(
'>');
955 const int atPos = addrSpec.lastIndexOf( QLatin1Char(
'@') );
960 QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() );
961 if ( idn.isEmpty() ) {
965 return addrSpec.left( atPos + 1 ) + idn;
971 const int atPos = addrSpec.lastIndexOf( QLatin1Char(
'@') );
976 QString idn = QLatin1String(KUrl::toAce( addrSpec.mid( atPos + 1 )) );
977 if ( idn.isEmpty() ) {
981 return addrSpec.left( atPos + 1 ) + idn;
988 if ( str.isEmpty() ) {
993 QStringList normalizedAddressList;
995 QByteArray displayName, addrSpec, comment;
997 for ( QStringList::ConstIterator it = addressList.begin();
998 ( it != addressList.end() );
1000 if ( !( *it ).isEmpty() ) {
1002 displayName, addrSpec, comment ) ==
AddressOk ) {
1004 displayName = KMime::decodeRFC2047String( displayName ).toUtf8();
1005 comment = KMime::decodeRFC2047String( comment ).toUtf8();
1007 normalizedAddressList
1009 fromIdn( QString::fromUtf8( addrSpec ) ),
1010 QString::fromUtf8( comment ) );
1019 return normalizedAddressList.join( QLatin1String(
", ") );
1026 if ( str.isEmpty() ) {
1031 QStringList normalizedAddressList;
1033 QByteArray displayName, addrSpec, comment;
1035 for ( QStringList::ConstIterator it = addressList.begin();
1036 ( it != addressList.end() );
1038 if ( !( *it ).isEmpty() ) {
1040 displayName, addrSpec, comment ) ==
AddressOk ) {
1043 toIdn( QString::fromUtf8( addrSpec ) ),
1044 QString::fromUtf8( comment ) );
1054 return normalizedAddressList.join( QLatin1String(
", ") );
1059static QString escapeQuotes(
const QString &str )
1061 if ( str.isEmpty() ) {
1067 escaped.reserve( 2 * str.length() );
1068 unsigned int len = 0;
1069 for (
int i = 0; i < str.length(); ++i, ++len ) {
1070 if ( str[i] == QLatin1Char(
'"') ) {
1071 escaped[len] = QLatin1Char(
'\\');
1073 }
else if ( str[i] == QLatin1Char(
'\\') ) {
1074 escaped[len] = QLatin1Char(
'\\');
1077 if ( i >= str.length() ) {
1081 escaped[len] = str[i];
1083 escaped.truncate( len );
1090 QString quoted = str;
1092 QRegExp needQuotes( QLatin1String(
"[^ 0-9A-Za-z\\x0080-\\xFFFF]") );
1094 if ( ( quoted[0] == QLatin1Char(
'"') ) && ( quoted[quoted.length() - 1] ==QLatin1Char(
'"') ) ) {
1095 quoted = QLatin1String(
"\"") + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + QLatin1String(
"\"");
1096 }
else if ( quoted.indexOf( needQuotes ) != -1 ) {
1097 quoted = QLatin1String(
"\"") + escapeQuotes( quoted ) + QLatin1String(
"\"");
1105 const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox,
"utf-8" );
1107 mailtoUrl.setProtocol( QLatin1String(
"mailto") );
1108 mailtoUrl.setPath( QLatin1String(encodedPath) );
1114 Q_ASSERT( mailtoUrl.protocol().toLower() == QLatin1String(
"mailto") );
1115 return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() );
This file is part of the KDEPIM Utilities library and provides static methods for email address valid...
QString decodeMailtoUrl(const KUrl &mailtoUrl)
Extracts the mailbox out of the mailto: URL.
KUrl encodeMailtoUrl(const QString &mailbox)
Creates a valid mailto: URL from the given mailbox.
QString toIdn(const QString &addrSpec)
Encodes the domain part of the given addr-spec in punycode if it's an IDN.
QString normalizeAddressesAndDecodeIdn(const QString &addresses)
Normalizes all email addresses in the given list and decodes all IDNs.
QString normalizeAddressesAndEncodeIdn(const QString &str)
Normalizes all email addresses in the given list and encodes all IDNs in punycode.
QString fromIdn(const QString &addrSpec)
Decodes the punycode domain part of the given addr-spec if it's an IDN.
EmailParseResult splitAddress(const QByteArray &address, QByteArray &displayName, QByteArray &addrSpec, QByteArray &comment)
Splits the given address into display name, email address and comment.
EmailParseResult isValidAddress(const QString &aStr)
Validates an email address in the form of "Joe User" joe@example.org.
EmailParseResult
Email validation result.
EmailParseResult isValidAddressList(const QString &aStr, QString &badAddr)
Validates a list of email addresses, and also allow aliases and distribution lists to be expanded bef...
QStringList splitAddressList(const QString &aStr)
Split a comma separated list of email addresses.
QString simpleEmailAddressErrorMsg()
Returns a i18n string to be used in msgboxes.
QString emailParseResultToString(EmailParseResult errorCode)
Translate the enum errorcodes from emailParseResult into i18n'd strings that can be used for msg boxe...
bool isValidSimpleAddress(const QString &aStr)
Validates an email address in the form of joe@example.org.
@ AddressEmpty
The address is empty.
@ TooManyAts
More than one @ in address.
@ UnopenedAngleAddr
> with no preceding <
@ UnbalancedQuote
Quotes (single or double) not matched.
@ InvalidDisplayName
An invalid displayname detected in address.
@ UnclosedAngleAddr
< with no matching >
@ UnexpectedComma
Comma not allowed here.
@ MissingLocalPart
No address specified, only domain.
@ TooFewAts
Missing @ in address.
@ AddressOk
Email is valid.
@ MissingDomainPart
No domain in address.
@ UnbalancedParens
Unbalanced ( )
@ DisallowedChar
An invalid character detected in address.
@ UnexpectedEnd
Something is unbalanced.