29 #include <kmime/kmime_util.h>
32 #include <KLocalizedString>
35 #include <QtCore/QRegExp>
36 #include <QtCore/QByteArray>
40 static const KCatalogLoader loader(
"libkpimutils" );
42 using 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() ) {
330 bool tooManyAtsFlag =
false;
332 int atCount = aStr.count(
'@' );
334 tooManyAtsFlag =
true;
335 }
else if ( atCount == 0 ) {
346 } context = TopLevel;
347 bool inQuotedString =
false;
348 int commentLevel = 0;
350 unsigned int strlen = aStr.length();
352 for (
unsigned int index = 0; index < strlen; index++ ) {
356 switch ( aStr[index].toLatin1() ) {
358 inQuotedString = !inQuotedString;
361 if ( !inQuotedString ) {
367 if ( !inQuotedString ) {
372 if ( !inQuotedString ) {
377 if ( !inQuotedString ) {
382 if ( !inQuotedString ) {
383 context = InAngleAddress;
388 if ( ( index + 1 ) > strlen ) {
393 if ( !inQuotedString ) {
398 if ( !inQuotedString ) {
403 if ( !inQuotedString ) {
408 if ( !inQuotedString ) {
411 }
else if ( index == strlen-1 ) {
414 }
else if ( inQuotedString ) {
416 if ( atCount == 1 ) {
417 tooManyAtsFlag =
false;
426 switch ( aStr[index].toLatin1() ) {
432 if ( commentLevel == 0 ) {
438 if ( ( index + 1 ) > strlen ) {
446 case InAngleAddress :
448 switch ( aStr[index].toLatin1() ) {
450 if ( !inQuotedString ) {
455 inQuotedString = !inQuotedString;
458 if ( inQuotedString ) {
460 if ( atCount == 1 ) {
461 tooManyAtsFlag =
false;
466 if ( !inQuotedString ) {
473 if ( ( index + 1 ) > strlen ) {
483 if ( atCount == 0 && !inQuotedString ) {
487 if ( inQuotedString ) {
491 if ( context == InComment ) {
495 if ( context == InAngleAddress ) {
499 if ( tooManyAtsFlag ) {
510 if ( aStr.isEmpty() ) {
516 QStringList::const_iterator it = list.begin();
518 for ( it = list.begin(); it != list.end(); ++it ) {
531 switch ( errorCode ) {
533 return i18n(
"The email address you entered is not valid because it "
534 "contains more than one @. "
535 "You will not create valid messages if you do not "
536 "change your address." );
538 return i18n(
"The email address you entered is not valid because it "
539 "does not contain a @. "
540 "You will not create valid messages if you do not "
541 "change your address." );
543 return i18n(
"You have to enter something in the email address field." );
545 return i18n(
"The email address you entered is not valid because it "
546 "does not contain a local part." );
548 return i18n(
"The email address you entered is not valid because it "
549 "does not contain a domain part." );
551 return i18n(
"The email address you entered is not valid because it "
552 "contains unclosed comments/brackets." );
554 return i18n(
"The email address you entered is valid." );
556 return i18n(
"The email address you entered is not valid because it "
557 "contains an unclosed angle bracket." );
559 return i18n(
"The email address you entered is not valid because it "
560 "contains too many closing angle brackets." );
562 return i18n(
"The email address you have entered is not valid because it "
563 "contains an unexpected comma." );
565 return i18n(
"The email address you entered is not valid because it ended "
566 "unexpectedly. This probably means you have used an escaping "
567 "type character like a '\\' as the last character in your "
570 return i18n(
"The email address you entered is not valid because it "
571 "contains quoted text which does not end." );
573 return i18n(
"The email address you entered is not valid because it "
574 "does not seem to contain an actual email address, i.e. "
575 "something of the form joe@example.org." );
577 return i18n(
"The email address you entered is not valid because it "
578 "contains an illegal character." );
580 return i18n(
"The email address you have entered is not valid because it "
581 "contains an invalid display name." );
583 return i18n(
"Unknown problem with email address" );
591 if ( aStr.isEmpty() ) {
595 int atChar = aStr.lastIndexOf(
'@' );
596 QString domainPart = aStr.mid( atChar + 1 );
597 QString localPart = aStr.left( atChar );
602 if ( localPart.isEmpty() || domainPart.isEmpty() ) {
606 bool tooManyAtsFlag =
false;
607 bool inQuotedString =
false;
608 int atCount = localPart.count(
'@' );
610 unsigned int strlen = localPart.length();
611 for (
unsigned int index = 0; index < strlen; index++ ) {
612 switch ( localPart[ index ].toLatin1() ) {
614 inQuotedString = !inQuotedString;
617 if ( inQuotedString ) {
619 if ( atCount == 0 ) {
620 tooManyAtsFlag =
false;
629 if ( localPart[ 0 ] ==
'\"' || localPart[ localPart.length()-1 ] ==
'\"' ) {
630 addrRx =
"\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@";
632 addrRx =
"[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@";
634 if ( domainPart[ 0 ] ==
'[' || domainPart[ domainPart.length()-1 ] ==
']' ) {
635 addrRx +=
"\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]";
637 addrRx +=
"[\\w-#]+(\\.[\\w-#]+)*";
639 QRegExp rx( addrRx );
640 return rx.exactMatch( aStr ) && !tooManyAtsFlag;
646 return i18n(
"The email address you entered is not valid because it "
647 "does not seem to contain an actual email address, i.e. "
648 "something of the form joe@example.org." );
654 QByteArray dummy1, dummy2, addrSpec;
656 splitAddressInternal( address, dummy1, addrSpec, dummy2,
659 addrSpec = QByteArray();
662 <<
"Input:" << address <<
"\nError:"
679 QByteArray dummy1, dummy2, addrSpec;
681 splitAddressInternal( addresses, dummy1, addrSpec, dummy2,
684 addrSpec = QByteArray();
687 <<
"Input: aStr\nError:"
703 QString &mail, QString &name )
708 const int len = aStr.length();
709 const char cQuotes =
'"';
711 bool bInComment =
false;
712 bool bInQuotesOutsideOfEmail =
false;
713 int i = 0, iAd = 0, iMailStart = 0, iMailEnd = 0;
715 unsigned int commentstack = 0;
727 bInComment = commentstack != 0;
728 if (
'"' == c && !bInComment ) {
729 bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail;
732 if ( !bInComment && !bInQuotesOutsideOfEmail ) {
745 for ( i = 0; len > i; ++i ) {
753 mail = aStr.mid( i + 1 );
754 if ( mail.endsWith(
'>' ) ) {
755 mail.truncate( mail.length() - 1 );
763 bInQuotesOutsideOfEmail =
false;
764 for ( i = iAd-1; 0 <= i; --i ) {
768 if ( !name.isEmpty() ) {
775 }
else if ( bInQuotesOutsideOfEmail ) {
776 if ( cQuotes == c ) {
777 bInQuotesOutsideOfEmail =
false;
778 }
else if ( c !=
'\\' ) {
788 if ( cQuotes == c ) {
789 bInQuotesOutsideOfEmail =
true;
794 switch ( c.toLatin1() ) {
799 if ( !name.isEmpty() ) {
813 name = name.simplified();
814 mail = mail.simplified();
816 if ( mail.isEmpty() ) {
826 bInQuotesOutsideOfEmail =
false;
827 int parenthesesNesting = 0;
828 for ( i = iAd+1; len > i; ++i ) {
832 if ( --parenthesesNesting == 0 ) {
834 if ( !name.isEmpty() ) {
844 ++parenthesesNesting;
848 }
else if ( bInQuotesOutsideOfEmail ) {
849 if ( cQuotes == c ) {
850 bInQuotesOutsideOfEmail =
false;
851 }
else if ( c !=
'\\' ) {
861 if ( cQuotes == c ) {
862 bInQuotesOutsideOfEmail =
true;
867 switch ( c.toLatin1() ) {
872 if ( !name.isEmpty() ) {
875 if ( ++parenthesesNesting > 0 ) {
889 name = name.simplified();
890 mail = mail.simplified();
892 return ! ( name.isEmpty() || mail.isEmpty() );
899 QString e1Name, e1Email, e2Name, e2Email;
904 return e1Email == e2Email &&
905 ( !matchName || ( e1Name == e2Name ) );
910 const QString &addrSpec,
911 const QString &comment )
913 const QString realDisplayName = KMime::removeBidiControlChars( displayName );
914 if ( realDisplayName.isEmpty() && comment.isEmpty() ) {
916 }
else if ( comment.isEmpty() ) {
917 if ( !realDisplayName.startsWith(
'\"' ) ) {
920 return realDisplayName +
" <" + addrSpec +
'>';
922 }
else if ( realDisplayName.isEmpty() ) {
923 QString commentStr = comment;
926 return realDisplayName +
" (" + comment +
") <" + addrSpec +
'>';
933 const int atPos = addrSpec.lastIndexOf(
'@' );
938 QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() );
939 if ( idn.isEmpty() ) {
943 return addrSpec.left( atPos + 1 ) + idn;
949 const int atPos = addrSpec.lastIndexOf(
'@' );
954 QString idn = KUrl::toAce( addrSpec.mid( atPos + 1 ) );
955 if ( idn.isEmpty() ) {
959 return addrSpec.left( atPos + 1 ) + idn;
966 if ( str.isEmpty() ) {
971 QStringList normalizedAddressList;
973 QByteArray displayName, addrSpec, comment;
975 for ( QStringList::ConstIterator it = addressList.begin();
976 ( it != addressList.end() );
978 if ( !( *it ).isEmpty() ) {
980 displayName, addrSpec, comment ) ==
AddressOk ) {
982 displayName = KMime::decodeRFC2047String( displayName ).toUtf8();
983 comment = KMime::decodeRFC2047String( comment ).toUtf8();
985 normalizedAddressList
987 fromIdn( QString::fromUtf8( addrSpec ) ),
988 QString::fromUtf8( comment ) );
997 return normalizedAddressList.join(
", " );
1004 if ( str.isEmpty() ) {
1009 QStringList normalizedAddressList;
1011 QByteArray displayName, addrSpec, comment;
1013 for ( QStringList::ConstIterator it = addressList.begin();
1014 ( it != addressList.end() );
1016 if ( !( *it ).isEmpty() ) {
1018 displayName, addrSpec, comment ) ==
AddressOk ) {
1021 toIdn( QString::fromUtf8( addrSpec ) ),
1022 QString::fromUtf8( comment ) );
1032 return normalizedAddressList.join(
", " );
1037 static QString escapeQuotes(
const QString &str )
1039 if ( str.isEmpty() ) {
1045 escaped.reserve( 2 * str.length() );
1046 unsigned int len = 0;
1047 for (
int i = 0; i < str.length(); ++i, ++len ) {
1048 if ( str[i] ==
'"' ) {
1049 escaped[len] =
'\\';
1051 }
else if ( str[i] ==
'\\' ) {
1052 escaped[len] =
'\\';
1055 if ( i >= str.length() ) {
1059 escaped[len] = str[i];
1061 escaped.truncate( len );
1068 QString quoted = str;
1070 QRegExp needQuotes(
"[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
1072 if ( ( quoted[0] ==
'"' ) && ( quoted[quoted.length() - 1] ==
'"' ) ) {
1073 quoted =
"\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) +
"\"";
1074 }
else if ( quoted.indexOf( needQuotes ) != -1 ) {
1075 quoted =
"\"" + escapeQuotes( quoted ) +
"\"";
1081 KUrl KPIMUtils::encodeMailtoUrl(
const QString &mailbox )
1083 const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox,
"utf-8" );
1085 mailtoUrl.setProtocol(
"mailto" );
1086 mailtoUrl.setPath( encodedPath );
1090 QString KPIMUtils::decodeMailtoUrl(
const KUrl &mailtoUrl )
1092 Q_ASSERT( mailtoUrl.protocol().toLower() ==
"mailto" );
1093 return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() );
EmailParseResult isValidAddress(const QString &aStr)
Validates an email address in the form of "Joe User" joe@example.org.
bool isValidSimpleAddress(const QString &aStr)
Validates an email address in the form of joe@example.org.
QString normalizeAddressesAndEncodeIdn(const QString &str)
Normalizes all email addresses in the given list and encodes all IDNs in punycode.
QString emailParseResultToString(EmailParseResult errorCode)
Translate the enum errorcodes from emailParseResult into i18n'd strings that can be used for msg boxe...
QStringList splitAddressList(const QString &aStr)
Split a comma separated list of email addresses.
No address specified, only domain.
This file is part of the KDEPIM Utilities library and provides static methods for email address valid...
More than one @ in address.
EmailParseResult splitAddress(const QByteArray &address, QByteArray &displayName, QByteArray &addrSpec, QByteArray &comment)
Splits the given address into display name, email address and comment.
An invalid displayname detected in address.
QString toIdn(const QString &addrSpec)
Encodes the domain part of the given addr-spec in punycode if it's an IDN.
EmailParseResult isValidAddressList(const QString &aStr, QString &badAddr)
Validates a list of email addresses, and also allow aliases and distribution lists to be expanded bef...
QString fromIdn(const QString &addrSpec)
Decodes the punycode domain part of the given addr-spec if it's an IDN.
> with no preceding <
QString normalizeAddressesAndDecodeIdn(const QString &addresses)
Normalizes all email addresses in the given list and decodes all IDNs.
QString simpleEmailAddressErrorMsg()
Returns a i18n string to be used in msgboxes.
EmailParseResult
Email validation result.
An invalid character detected in address.
Quotes (single or double) not matched.
< with no matching >