29 #include <kmime/kmime_util.h>
32 #include <KLocalizedString>
35 #include <QtCore/QRegExp>
36 #include <QtCore/QByteArray>
40 static const KCatalogLoader loader( QLatin1String(
"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() ) {
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(
", ") );
1059 static 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(
"\"");
1103 KUrl KPIMUtils::encodeMailtoUrl(
const QString &mailbox )
1105 const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox,
"utf-8" );
1107 mailtoUrl.setProtocol( QLatin1String(
"mailto") );
1108 mailtoUrl.setPath( QLatin1String(encodedPath) );
1112 QString KPIMUtils::decodeMailtoUrl(
const KUrl &mailtoUrl )
1114 Q_ASSERT( mailtoUrl.protocol().toLower() == QLatin1String(
"mailto") );
1115 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.
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.