29 #include <kmime/kmime_util.h>
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() );