kpimutils
email.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the kpimutils library. 00003 Copyright (c) 2004 Matt Douhan <matt@fruitsalad.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00027 #include "email.h" 00028 00029 #include <kmime/kmime_util.h> 00030 00031 #include <KDebug> 00032 #include <KLocale> 00033 #include <KUrl> 00034 00035 #include <QtCore/QRegExp> 00036 #include <QtCore/QByteArray> 00037 00038 using namespace KPIMUtils; 00039 00040 //----------------------------------------------------------------------------- 00041 QStringList KPIMUtils::splitAddressList( const QString &aStr ) 00042 { 00043 // Features: 00044 // - always ignores quoted characters 00045 // - ignores everything (including parentheses and commas) 00046 // inside quoted strings 00047 // - supports nested comments 00048 // - ignores everything (including double quotes and commas) 00049 // inside comments 00050 00051 QStringList list; 00052 00053 if ( aStr.isEmpty() ) { 00054 return list; 00055 } 00056 00057 QString addr; 00058 uint addrstart = 0; 00059 int commentlevel = 0; 00060 bool insidequote = false; 00061 00062 for ( int index=0; index<aStr.length(); index++ ) { 00063 // the following conversion to latin1 is o.k. because 00064 // we can safely ignore all non-latin1 characters 00065 switch ( aStr[index].toLatin1() ) { 00066 case '"' : // start or end of quoted string 00067 if ( commentlevel == 0 ) { 00068 insidequote = !insidequote; 00069 } 00070 break; 00071 case '(' : // start of comment 00072 if ( !insidequote ) { 00073 commentlevel++; 00074 } 00075 break; 00076 case ')' : // end of comment 00077 if ( !insidequote ) { 00078 if ( commentlevel > 0 ) { 00079 commentlevel--; 00080 } else { 00081 return list; 00082 } 00083 } 00084 break; 00085 case '\\' : // quoted character 00086 index++; // ignore the quoted character 00087 break; 00088 case ',' : 00089 case ';' : 00090 if ( !insidequote && ( commentlevel == 0 ) ) { 00091 addr = aStr.mid( addrstart, index - addrstart ); 00092 if ( !addr.isEmpty() ) { 00093 list += addr.simplified(); 00094 } 00095 addrstart = index + 1; 00096 } 00097 break; 00098 } 00099 } 00100 // append the last address to the list 00101 if ( !insidequote && ( commentlevel == 0 ) ) { 00102 addr = aStr.mid( addrstart, aStr.length() - addrstart ); 00103 if ( !addr.isEmpty() ) { 00104 list += addr.simplified(); 00105 } 00106 } 00107 00108 return list; 00109 } 00110 00111 //----------------------------------------------------------------------------- 00112 // Used by KPIMUtils::splitAddress(...) and KPIMUtils::firstEmailAddress(...). 00113 KPIMUtils::EmailParseResult splitAddressInternal( const QByteArray address, 00114 QByteArray &displayName, 00115 QByteArray &addrSpec, 00116 QByteArray &comment, 00117 bool allowMultipleAddresses ) 00118 { 00119 // kDebug() << "address"; 00120 displayName = ""; 00121 addrSpec = ""; 00122 comment = ""; 00123 00124 if ( address.isEmpty() ) { 00125 return AddressEmpty; 00126 } 00127 00128 // The following is a primitive parser for a mailbox-list (cf. RFC 2822). 00129 // The purpose is to extract a displayable string from the mailboxes. 00130 // Comments in the addr-spec are not handled. No error checking is done. 00131 00132 enum { 00133 TopLevel, 00134 InComment, 00135 InAngleAddress 00136 } context = TopLevel; 00137 bool inQuotedString = false; 00138 int commentLevel = 0; 00139 bool stop = false; 00140 00141 for ( const char *p = address.data(); *p && !stop; ++p ) { 00142 switch ( context ) { 00143 case TopLevel : 00144 { 00145 switch ( *p ) { 00146 case '"' : 00147 inQuotedString = !inQuotedString; 00148 displayName += *p; 00149 break; 00150 case '(' : 00151 if ( !inQuotedString ) { 00152 context = InComment; 00153 commentLevel = 1; 00154 } else { 00155 displayName += *p; 00156 } 00157 break; 00158 case '<' : 00159 if ( !inQuotedString ) { 00160 context = InAngleAddress; 00161 } else { 00162 displayName += *p; 00163 } 00164 break; 00165 case '\\' : // quoted character 00166 displayName += *p; 00167 ++p; // skip the '\' 00168 if ( *p ) { 00169 displayName += *p; 00170 } else { 00171 return UnexpectedEnd; 00172 } 00173 break; 00174 case ',' : 00175 if ( !inQuotedString ) { 00176 if ( allowMultipleAddresses ) { 00177 stop = true; 00178 } else { 00179 return UnexpectedComma; 00180 } 00181 } else { 00182 displayName += *p; 00183 } 00184 break; 00185 default : 00186 displayName += *p; 00187 } 00188 break; 00189 } 00190 case InComment : 00191 { 00192 switch ( *p ) { 00193 case '(' : 00194 ++commentLevel; 00195 comment += *p; 00196 break; 00197 case ')' : 00198 --commentLevel; 00199 if ( commentLevel == 0 ) { 00200 context = TopLevel; 00201 comment += ' '; // separate the text of several comments 00202 } else { 00203 comment += *p; 00204 } 00205 break; 00206 case '\\' : // quoted character 00207 comment += *p; 00208 ++p; // skip the '\' 00209 if ( *p ) { 00210 comment += *p; 00211 } else { 00212 return UnexpectedEnd; 00213 } 00214 break; 00215 default : 00216 comment += *p; 00217 } 00218 break; 00219 } 00220 case InAngleAddress : 00221 { 00222 switch ( *p ) { 00223 case '"' : 00224 inQuotedString = !inQuotedString; 00225 addrSpec += *p; 00226 break; 00227 case '>' : 00228 if ( !inQuotedString ) { 00229 context = TopLevel; 00230 } else { 00231 addrSpec += *p; 00232 } 00233 break; 00234 case '\\' : // quoted character 00235 addrSpec += *p; 00236 ++p; // skip the '\' 00237 if ( *p ) { 00238 addrSpec += *p; 00239 } else { 00240 return UnexpectedEnd; 00241 } 00242 break; 00243 default : 00244 addrSpec += *p; 00245 } 00246 break; 00247 } 00248 } // switch ( context ) 00249 } 00250 // check for errors 00251 if ( inQuotedString ) { 00252 return UnbalancedQuote; 00253 } 00254 if ( context == InComment ) { 00255 return UnbalancedParens; 00256 } 00257 if ( context == InAngleAddress ) { 00258 return UnclosedAngleAddr; 00259 } 00260 00261 displayName = displayName.trimmed(); 00262 comment = comment.trimmed(); 00263 addrSpec = addrSpec.trimmed(); 00264 00265 if ( addrSpec.isEmpty() ) { 00266 if ( displayName.isEmpty() ) { 00267 return NoAddressSpec; 00268 } else { 00269 addrSpec = displayName; 00270 displayName.truncate( 0 ); 00271 } 00272 } 00273 /* 00274 kDebug() << "display-name : \"" << displayName << "\""; 00275 kDebug() << "comment : \"" << comment << "\""; 00276 kDebug() << "addr-spec : \"" << addrSpec << "\""; 00277 */ 00278 return AddressOk; 00279 } 00280 00281 //----------------------------------------------------------------------------- 00282 EmailParseResult KPIMUtils::splitAddress( const QByteArray &address, 00283 QByteArray &displayName, 00284 QByteArray &addrSpec, 00285 QByteArray &comment ) 00286 { 00287 return splitAddressInternal( address, displayName, addrSpec, comment, 00288 false/* don't allow multiple addresses */ ); 00289 } 00290 00291 //----------------------------------------------------------------------------- 00292 EmailParseResult KPIMUtils::splitAddress( const QString &address, 00293 QString &displayName, 00294 QString &addrSpec, 00295 QString &comment ) 00296 { 00297 QByteArray d, a, c; 00298 EmailParseResult result = splitAddress( address.toUtf8(), d, a, c ); 00299 00300 if ( result == AddressOk ) { 00301 displayName = QString::fromUtf8( d ); 00302 addrSpec = QString::fromUtf8( a ); 00303 comment = QString::fromUtf8( c ); 00304 } 00305 return result; 00306 } 00307 00308 //----------------------------------------------------------------------------- 00309 EmailParseResult KPIMUtils::isValidAddress( const QString &aStr ) 00310 { 00311 // If we are passed an empty string bail right away no need to process 00312 // further and waste resources 00313 if ( aStr.isEmpty() ) { 00314 return AddressEmpty; 00315 } 00316 00317 // count how many @'s are in the string that is passed to us 00318 // if 0 or > 1 take action 00319 // at this point to many @'s cannot bail out right away since 00320 // @ is allowed in qoutes, so we use a bool to keep track 00321 // and then make a judgment further down in the parser 00322 // FIXME count only @ not in double quotes 00323 00324 bool tooManyAtsFlag = false; 00325 00326 int atCount = aStr.count( '@' ); 00327 if ( atCount > 1 ) { 00328 tooManyAtsFlag = true; 00329 } else if ( atCount == 0 ) { 00330 return TooFewAts; 00331 } 00332 00333 // The main parser, try and catch all weird and wonderful 00334 // mistakes users and/or machines can create 00335 00336 enum { 00337 TopLevel, 00338 InComment, 00339 InAngleAddress 00340 } context = TopLevel; 00341 bool inQuotedString = false; 00342 int commentLevel = 0; 00343 00344 unsigned int strlen = aStr.length(); 00345 00346 for ( unsigned int index=0; index < strlen; index++ ) { 00347 switch ( context ) { 00348 case TopLevel : 00349 { 00350 switch ( aStr[index].toLatin1() ) { 00351 case '"' : 00352 inQuotedString = !inQuotedString; 00353 break; 00354 case '(' : 00355 if ( !inQuotedString ) { 00356 context = InComment; 00357 commentLevel = 1; 00358 } 00359 break; 00360 case '[' : 00361 if ( !inQuotedString ) { 00362 return InvalidDisplayName; 00363 } 00364 break; 00365 case ']' : 00366 if ( !inQuotedString ) { 00367 return InvalidDisplayName; 00368 } 00369 break; 00370 case ':' : 00371 if ( !inQuotedString ) { 00372 return DisallowedChar; 00373 } 00374 break; 00375 case '<' : 00376 if ( !inQuotedString ) { 00377 context = InAngleAddress; 00378 } 00379 break; 00380 case '\\' : // quoted character 00381 ++index; // skip the '\' 00382 if ( ( index + 1 ) > strlen ) { 00383 return UnexpectedEnd; 00384 } 00385 break; 00386 case ',' : 00387 if ( !inQuotedString ) { 00388 return UnexpectedComma; 00389 } 00390 break; 00391 case ')' : 00392 if ( !inQuotedString ) { 00393 return UnbalancedParens; 00394 } 00395 break; 00396 case '>' : 00397 if ( !inQuotedString ) { 00398 return UnopenedAngleAddr; 00399 } 00400 break; 00401 case '@' : 00402 if ( !inQuotedString ) { 00403 if ( index == 0 ) { // Missing local part 00404 return MissingLocalPart; 00405 } else if ( index == strlen-1 ) { 00406 return MissingDomainPart; 00407 break; 00408 } 00409 } else if ( inQuotedString ) { 00410 --atCount; 00411 if ( atCount == 1 ) { 00412 tooManyAtsFlag = false; 00413 } 00414 } 00415 break; 00416 } 00417 break; 00418 } 00419 case InComment : 00420 { 00421 switch ( aStr[index].toLatin1() ) { 00422 case '(' : 00423 ++commentLevel; 00424 break; 00425 case ')' : 00426 --commentLevel; 00427 if ( commentLevel == 0 ) { 00428 context = TopLevel; 00429 } 00430 break; 00431 case '\\' : // quoted character 00432 ++index; // skip the '\' 00433 if ( ( index + 1 ) > strlen ) { 00434 return UnexpectedEnd; 00435 } 00436 break; 00437 } 00438 break; 00439 } 00440 00441 case InAngleAddress : 00442 { 00443 switch ( aStr[index].toLatin1() ) { 00444 case ',' : 00445 if ( !inQuotedString ) { 00446 return UnexpectedComma; 00447 } 00448 break; 00449 case '"' : 00450 inQuotedString = !inQuotedString; 00451 break; 00452 case '@' : 00453 if ( inQuotedString ) { 00454 --atCount; 00455 if ( atCount == 1 ) { 00456 tooManyAtsFlag = false; 00457 } 00458 } 00459 break; 00460 case '>' : 00461 if ( !inQuotedString ) { 00462 context = TopLevel; 00463 break; 00464 } 00465 break; 00466 case '\\' : // quoted character 00467 ++index; // skip the '\' 00468 if ( ( index + 1 ) > strlen ) { 00469 return UnexpectedEnd; 00470 } 00471 break; 00472 } 00473 break; 00474 } 00475 } 00476 } 00477 00478 if ( atCount == 0 && !inQuotedString ) { 00479 return TooFewAts; 00480 } 00481 00482 if ( inQuotedString ) { 00483 return UnbalancedQuote; 00484 } 00485 00486 if ( context == InComment ) { 00487 return UnbalancedParens; 00488 } 00489 00490 if ( context == InAngleAddress ) { 00491 return UnclosedAngleAddr; 00492 } 00493 00494 if ( tooManyAtsFlag ) { 00495 return TooManyAts; 00496 } 00497 00498 return AddressOk; 00499 } 00500 00501 //----------------------------------------------------------------------------- 00502 KPIMUtils::EmailParseResult KPIMUtils::isValidAddressList( const QString &aStr, 00503 QString &badAddr ) 00504 { 00505 if ( aStr.isEmpty() ) { 00506 return AddressEmpty; 00507 } 00508 00509 const QStringList list = splitAddressList( aStr ); 00510 00511 QStringList::const_iterator it = list.begin(); 00512 EmailParseResult errorCode = AddressOk; 00513 for ( it = list.begin(); it != list.end(); ++it ) { 00514 errorCode = isValidAddress( *it ); 00515 if ( errorCode != AddressOk ) { 00516 badAddr = ( *it ); 00517 break; 00518 } 00519 } 00520 return errorCode; 00521 } 00522 00523 //----------------------------------------------------------------------------- 00524 QString KPIMUtils::emailParseResultToString( EmailParseResult errorCode ) 00525 { 00526 switch ( errorCode ) { 00527 case TooManyAts : 00528 return i18n( "The email address you entered is not valid because it " 00529 "contains more than one @. " 00530 "You will not create valid messages if you do not " 00531 "change your address." ); 00532 case TooFewAts : 00533 return i18n( "The email address you entered is not valid because it " 00534 "does not contain a @. " 00535 "You will not create valid messages if you do not " 00536 "change your address." ); 00537 case AddressEmpty : 00538 return i18n( "You have to enter something in the email address field." ); 00539 case MissingLocalPart : 00540 return i18n( "The email address you entered is not valid because it " 00541 "does not contain a local part." ); 00542 case MissingDomainPart : 00543 return i18n( "The email address you entered is not valid because it " 00544 "does not contain a domain part." ); 00545 case UnbalancedParens : 00546 return i18n( "The email address you entered is not valid because it " 00547 "contains unclosed comments/brackets." ); 00548 case AddressOk : 00549 return i18n( "The email address you entered is valid." ); 00550 case UnclosedAngleAddr : 00551 return i18n( "The email address you entered is not valid because it " 00552 "contains an unclosed angle bracket." ); 00553 case UnopenedAngleAddr : 00554 return i18n( "The email address you entered is not valid because it " 00555 "contains too many closing angle brackets." ); 00556 case UnexpectedComma : 00557 return i18n( "The email address you have entered is not valid because it " 00558 "contains an unexpected comma." ); 00559 case UnexpectedEnd : 00560 return i18n( "The email address you entered is not valid because it ended " 00561 "unexpectedly. This probably means you have used an escaping " 00562 "type character like a '\\' as the last character in your " 00563 "email address." ); 00564 case UnbalancedQuote : 00565 return i18n( "The email address you entered is not valid because it " 00566 "contains quoted text which does not end." ); 00567 case NoAddressSpec : 00568 return i18n( "The email address you entered is not valid because it " 00569 "does not seem to contain an actual email address, i.e. " 00570 "something of the form joe@example.org." ); 00571 case DisallowedChar : 00572 return i18n( "The email address you entered is not valid because it " 00573 "contains an illegal character." ); 00574 case InvalidDisplayName : 00575 return i18n( "The email address you have entered is not valid because it " 00576 "contains an invalid display name." ); 00577 } 00578 return i18n( "Unknown problem with email address" ); 00579 } 00580 00581 //----------------------------------------------------------------------------- 00582 bool KPIMUtils::isValidSimpleAddress( const QString &aStr ) 00583 { 00584 // If we are passed an empty string bail right away no need to process further 00585 // and waste resources 00586 if ( aStr.isEmpty() ) { 00587 return false; 00588 } 00589 00590 int atChar = aStr.lastIndexOf( '@' ); 00591 QString domainPart = aStr.mid( atChar + 1 ); 00592 QString localPart = aStr.left( atChar ); 00593 00594 // Both of these parts must be non empty 00595 // after all we cannot have emails like: 00596 // @kde.org, or foo@ 00597 if ( localPart.isEmpty() || domainPart.isEmpty() ) { 00598 return false; 00599 } 00600 00601 bool tooManyAtsFlag = false; 00602 bool inQuotedString = false; 00603 int atCount = localPart.count( '@' ); 00604 00605 unsigned int strlen = localPart.length(); 00606 for ( unsigned int index=0; index < strlen; index++ ) { 00607 switch( localPart[ index ].toLatin1() ) { 00608 case '"' : 00609 inQuotedString = !inQuotedString; 00610 break; 00611 case '@' : 00612 if ( inQuotedString ) { 00613 --atCount; 00614 if ( atCount == 0 ) { 00615 tooManyAtsFlag = false; 00616 } 00617 } 00618 break; 00619 } 00620 } 00621 00622 QString addrRx = 00623 "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@"; 00624 00625 if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) { 00626 addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@"; 00627 } 00628 if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) { 00629 addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]"; 00630 } else { 00631 addrRx += "[\\w-]+(\\.[\\w-]+)*"; 00632 } 00633 QRegExp rx( addrRx ); 00634 return rx.exactMatch( aStr ) && !tooManyAtsFlag; 00635 } 00636 00637 //----------------------------------------------------------------------------- 00638 QString KPIMUtils::simpleEmailAddressErrorMsg() 00639 { 00640 return i18n( "The email address you entered is not valid because it " 00641 "does not seem to contain an actual email address, i.e. " 00642 "something of the form joe@example.org." ); 00643 } 00644 00645 //----------------------------------------------------------------------------- 00646 QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address ) 00647 { 00648 QByteArray dummy1, dummy2, addrSpec; 00649 EmailParseResult result = 00650 splitAddressInternal( address, dummy1, addrSpec, dummy2, 00651 false/* don't allow multiple addresses */ ); 00652 if ( result != AddressOk ) { 00653 addrSpec = QByteArray(); 00654 if ( result != AddressEmpty ) { 00655 kDebug() 00656 << "Input:" << address << "\nError:" 00657 << emailParseResultToString( result ); 00658 } 00659 } 00660 00661 return addrSpec; 00662 } 00663 00664 //----------------------------------------------------------------------------- 00665 QString KPIMUtils::extractEmailAddress( const QString &address ) 00666 { 00667 return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) ); 00668 } 00669 00670 //----------------------------------------------------------------------------- 00671 QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses ) 00672 { 00673 QByteArray dummy1, dummy2, addrSpec; 00674 EmailParseResult result = 00675 splitAddressInternal( addresses, dummy1, addrSpec, dummy2, 00676 true/* allow multiple addresses */ ); 00677 if ( result != AddressOk ) { 00678 addrSpec = QByteArray(); 00679 if ( result != AddressEmpty ) { 00680 kDebug() 00681 << "Input: aStr\nError:" 00682 << emailParseResultToString( result ); 00683 } 00684 } 00685 00686 return addrSpec; 00687 } 00688 00689 //----------------------------------------------------------------------------- 00690 QString KPIMUtils::firstEmailAddress( const QString &addresses ) 00691 { 00692 return QString::fromUtf8( firstEmailAddress( addresses.toUtf8() ) ); 00693 } 00694 00695 //----------------------------------------------------------------------------- 00696 bool KPIMUtils::extractEmailAddressAndName( const QString &aStr, 00697 QString &mail, QString &name ) 00698 { 00699 name.clear(); 00700 mail.clear(); 00701 00702 const int len = aStr.length(); 00703 const char cQuotes = '"'; 00704 00705 bool bInComment = false; 00706 bool bInQuotesOutsideOfEmail = false; 00707 int i=0, iAd=0, iMailStart=0, iMailEnd=0; 00708 QChar c; 00709 unsigned int commentstack = 0; 00710 00711 // Find the '@' of the email address 00712 // skipping all '@' inside "(...)" comments: 00713 while ( i < len ) { 00714 c = aStr[i]; 00715 if ( '(' == c ) { 00716 commentstack++; 00717 } 00718 if ( ')' == c ) { 00719 commentstack--; 00720 } 00721 bInComment = commentstack != 0; 00722 if ( '"' == c && !bInComment ) { 00723 bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail; 00724 } 00725 00726 if( !bInComment && !bInQuotesOutsideOfEmail ) { 00727 if ( '@' == c ) { 00728 iAd = i; 00729 break; // found it 00730 } 00731 } 00732 ++i; 00733 } 00734 00735 if ( !iAd ) { 00736 // We suppose the user is typing the string manually and just 00737 // has not finished typing the mail address part. 00738 // So we take everything that's left of the '<' as name and the rest as mail 00739 for ( i = 0; len > i; ++i ) { 00740 c = aStr[i]; 00741 if ( '<' != c ) { 00742 name.append( c ); 00743 } else { 00744 break; 00745 } 00746 } 00747 mail = aStr.mid( i + 1 ); 00748 if ( mail.endsWith( '>' ) ) { 00749 mail.truncate( mail.length() - 1 ); 00750 } 00751 00752 } else { 00753 // Loop backwards until we find the start of the string 00754 // or a ',' that is outside of a comment 00755 // and outside of quoted text before the leading '<'. 00756 bInComment = false; 00757 bInQuotesOutsideOfEmail = false; 00758 for ( i = iAd-1; 0 <= i; --i ) { 00759 c = aStr[i]; 00760 if ( bInComment ) { 00761 if ( '(' == c ) { 00762 if ( !name.isEmpty() ) { 00763 name.prepend( ' ' ); 00764 } 00765 bInComment = false; 00766 } else { 00767 name.prepend( c ); // all comment stuff is part of the name 00768 } 00769 } else if ( bInQuotesOutsideOfEmail ) { 00770 if ( cQuotes == c ) { 00771 bInQuotesOutsideOfEmail = false; 00772 } else if ( c != '\\' ) { 00773 name.prepend( c ); 00774 } 00775 } else { 00776 // found the start of this addressee ? 00777 if ( ',' == c ) { 00778 break; 00779 } 00780 // stuff is before the leading '<' ? 00781 if ( iMailStart ) { 00782 if ( cQuotes == c ) { 00783 bInQuotesOutsideOfEmail = true; // end of quoted text found 00784 } else { 00785 name.prepend( c ); 00786 } 00787 } else { 00788 switch ( c.toLatin1() ) { 00789 case '<': 00790 iMailStart = i; 00791 break; 00792 case ')': 00793 if ( !name.isEmpty() ) { 00794 name.prepend( ' ' ); 00795 } 00796 bInComment = true; 00797 break; 00798 default: 00799 if ( ' ' != c ) { 00800 mail.prepend( c ); 00801 } 00802 } 00803 } 00804 } 00805 } 00806 00807 name = name.simplified(); 00808 mail = mail.simplified(); 00809 00810 if ( mail.isEmpty() ) { 00811 return false; 00812 } 00813 00814 mail.append( '@' ); 00815 00816 // Loop forward until we find the end of the string 00817 // or a ',' that is outside of a comment 00818 // and outside of quoted text behind the trailing '>'. 00819 bInComment = false; 00820 bInQuotesOutsideOfEmail = false; 00821 int parenthesesNesting = 0; 00822 for ( i = iAd+1; len > i; ++i ) { 00823 c = aStr[i]; 00824 if ( bInComment ) { 00825 if ( ')' == c ) { 00826 if ( --parenthesesNesting == 0 ) { 00827 bInComment = false; 00828 if ( !name.isEmpty() ) { 00829 name.append( ' ' ); 00830 } 00831 } else { 00832 // nested ")", add it 00833 name.append( ')' ); // name can't be empty here 00834 } 00835 } else { 00836 if ( '(' == c ) { 00837 // nested "(" 00838 ++parenthesesNesting; 00839 } 00840 name.append( c ); // all comment stuff is part of the name 00841 } 00842 } else if ( bInQuotesOutsideOfEmail ) { 00843 if ( cQuotes == c ) { 00844 bInQuotesOutsideOfEmail = false; 00845 } else if ( c != '\\' ) { 00846 name.append( c ); 00847 } 00848 } else { 00849 // found the end of this addressee ? 00850 if ( ',' == c ) { 00851 break; 00852 } 00853 // stuff is behind the trailing '>' ? 00854 if ( iMailEnd ){ 00855 if ( cQuotes == c ) { 00856 bInQuotesOutsideOfEmail = true; // start of quoted text found 00857 } else { 00858 name.append( c ); 00859 } 00860 } else { 00861 switch ( c.toLatin1() ) { 00862 case '>': 00863 iMailEnd = i; 00864 break; 00865 case '(': 00866 if ( !name.isEmpty() ) { 00867 name.append( ' ' ); 00868 } 00869 if ( ++parenthesesNesting > 0 ) { 00870 bInComment = true; 00871 } 00872 break; 00873 default: 00874 if ( ' ' != c ) { 00875 mail.append( c ); 00876 } 00877 } 00878 } 00879 } 00880 } 00881 } 00882 00883 name = name.simplified(); 00884 mail = mail.simplified(); 00885 00886 return ! ( name.isEmpty() || mail.isEmpty() ); 00887 } 00888 00889 //----------------------------------------------------------------------------- 00890 bool KPIMUtils::compareEmail( const QString &email1, const QString &email2, 00891 bool matchName ) 00892 { 00893 QString e1Name, e1Email, e2Name, e2Email; 00894 00895 extractEmailAddressAndName( email1, e1Email, e1Name ); 00896 extractEmailAddressAndName( email2, e2Email, e2Name ); 00897 00898 return e1Email == e2Email && 00899 ( !matchName || ( e1Name == e2Name ) ); 00900 } 00901 00902 //----------------------------------------------------------------------------- 00903 QString KPIMUtils::normalizedAddress( const QString &displayName, 00904 const QString &addrSpec, 00905 const QString &comment ) 00906 { 00907 const QString realDisplayName = KMime::removeBidiControlChars( displayName ); 00908 if ( realDisplayName.isEmpty() && comment.isEmpty() ) { 00909 return addrSpec; 00910 } else if ( comment.isEmpty() ) { 00911 if ( !realDisplayName.startsWith( '\"' ) ) { 00912 return quoteNameIfNecessary( realDisplayName ) + " <" + addrSpec + '>'; 00913 } else { 00914 return realDisplayName + " <" + addrSpec + '>'; 00915 } 00916 } else if ( realDisplayName.isEmpty() ) { 00917 QString commentStr = comment; 00918 return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + '>'; 00919 } else { 00920 return realDisplayName + " (" + comment + ") <" + addrSpec + '>'; 00921 } 00922 } 00923 00924 //----------------------------------------------------------------------------- 00925 QString KPIMUtils::fromIdn( const QString &addrSpec ) 00926 { 00927 const int atPos = addrSpec.lastIndexOf( '@' ); 00928 if ( atPos == -1 ) { 00929 return addrSpec; 00930 } 00931 00932 QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() ); 00933 if ( idn.isEmpty() ) { 00934 return QString(); 00935 } 00936 00937 return addrSpec.left( atPos + 1 ) + idn; 00938 } 00939 00940 //----------------------------------------------------------------------------- 00941 QString KPIMUtils::toIdn( const QString &addrSpec ) 00942 { 00943 const int atPos = addrSpec.lastIndexOf( '@' ); 00944 if ( atPos == -1 ) { 00945 return addrSpec; 00946 } 00947 00948 QString idn = KUrl::toAce( addrSpec.mid( atPos + 1 ) ); 00949 if ( idn.isEmpty() ) { 00950 return addrSpec; 00951 } 00952 00953 return addrSpec.left( atPos + 1 ) + idn; 00954 } 00955 00956 //----------------------------------------------------------------------------- 00957 QString KPIMUtils::normalizeAddressesAndDecodeIdn( const QString &str ) 00958 { 00959 // kDebug() << str; 00960 if ( str.isEmpty() ) { 00961 return str; 00962 } 00963 00964 const QStringList addressList = splitAddressList( str ); 00965 QStringList normalizedAddressList; 00966 00967 QByteArray displayName, addrSpec, comment; 00968 00969 for ( QStringList::ConstIterator it = addressList.begin(); 00970 ( it != addressList.end() ); 00971 ++it ) { 00972 if ( !(*it).isEmpty() ) { 00973 if ( splitAddress( (*it).toUtf8(), 00974 displayName, addrSpec, comment ) == AddressOk ) { 00975 00976 displayName = KMime::decodeRFC2047String(displayName).toUtf8(); 00977 comment = KMime::decodeRFC2047String(comment).toUtf8(); 00978 00979 normalizedAddressList 00980 << normalizedAddress( QString::fromUtf8( displayName ), 00981 fromIdn( QString::fromUtf8( addrSpec ) ), 00982 QString::fromUtf8( comment ) ); 00983 } 00984 } 00985 } 00986 /* 00987 kDebug() << "normalizedAddressList: \"" 00988 << normalizedAddressList.join( ", " ) 00989 << "\""; 00990 */ 00991 return normalizedAddressList.join( ", " ); 00992 } 00993 00994 //----------------------------------------------------------------------------- 00995 QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str ) 00996 { 00997 //kDebug() << str; 00998 if ( str.isEmpty() ) { 00999 return str; 01000 } 01001 01002 const QStringList addressList = splitAddressList( str ); 01003 QStringList normalizedAddressList; 01004 01005 QByteArray displayName, addrSpec, comment; 01006 01007 for ( QStringList::ConstIterator it = addressList.begin(); 01008 ( it != addressList.end() ); 01009 ++it ) { 01010 if ( !(*it).isEmpty() ) { 01011 if ( splitAddress( (*it).toUtf8(), 01012 displayName, addrSpec, comment ) == AddressOk ) { 01013 01014 normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ), 01015 toIdn( QString::fromUtf8( addrSpec ) ), 01016 QString::fromUtf8( comment ) ); 01017 } 01018 } 01019 } 01020 01021 /* 01022 kDebug() << "normalizedAddressList: \"" 01023 << normalizedAddressList.join( ", " ) 01024 << "\""; 01025 */ 01026 return normalizedAddressList.join( ", " ); 01027 } 01028 01029 //----------------------------------------------------------------------------- 01030 // Escapes unescaped doublequotes in str. 01031 static QString escapeQuotes( const QString &str ) 01032 { 01033 if ( str.isEmpty() ) { 01034 return QString(); 01035 } 01036 01037 QString escaped; 01038 // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" ) 01039 escaped.reserve( 2 * str.length() ); 01040 unsigned int len = 0; 01041 for ( int i = 0; i < str.length(); ++i, ++len ) { 01042 if ( str[i] == '"' ) { // unescaped doublequote 01043 escaped[len] = '\\'; 01044 ++len; 01045 } else if ( str[i] == '\\' ) { // escaped character 01046 escaped[len] = '\\'; 01047 ++len; 01048 ++i; 01049 if ( i >= str.length() ) { // handle trailing '\' gracefully 01050 break; 01051 } 01052 } 01053 escaped[len] = str[i]; 01054 } 01055 escaped.truncate( len ); 01056 return escaped; 01057 } 01058 01059 //----------------------------------------------------------------------------- 01060 QString KPIMUtils::quoteNameIfNecessary( const QString &str ) 01061 { 01062 QString quoted = str; 01063 01064 QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ); 01065 // avoid double quoting 01066 if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) { 01067 quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\""; 01068 } else if ( quoted.indexOf( needQuotes ) != -1 ) { 01069 quoted = "\"" + escapeQuotes( quoted ) + "\""; 01070 } 01071 01072 return quoted; 01073 } 01074 01075 KUrl KPIMUtils::encodeMailtoUrl( const QString &mailbox ) 01076 { 01077 const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox, "utf-8" ); 01078 KUrl mailtoUrl; 01079 mailtoUrl.setProtocol( "mailto" ); 01080 mailtoUrl.setPath( encodedPath ); 01081 return mailtoUrl; 01082 } 01083 01084 QString KPIMUtils::decodeMailtoUrl( const KUrl &mailtoUrl ) 01085 { 01086 Q_ASSERT( mailtoUrl.protocol().toLower() == "mailto" ); 01087 return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() ); 01088 }