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 */ 00028 #include "email.h" 00029 00030 #include <kdebug.h> 00031 #include <klocale.h> 00032 #include <kurl.h> 00033 #include <kmime_util.h> 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 bool tooManyAtsFlag = false; 00601 bool inQuotedString = false; 00602 int atCount = localPart.count( '@' ); 00603 00604 unsigned int strlen = localPart.length(); 00605 for ( unsigned int index=0; index < strlen; index++ ) { 00606 switch( localPart[ index ].toLatin1() ) { 00607 case '"' : 00608 inQuotedString = !inQuotedString; 00609 break; 00610 case '@' : 00611 if ( inQuotedString ) { 00612 --atCount; 00613 if ( atCount == 0 ) { 00614 tooManyAtsFlag = false; 00615 } 00616 } 00617 break; 00618 } 00619 } 00620 00621 QString addrRx = 00622 "[a-zA-Z]*[~|{}`\\^?=/+*'&%$#!_\\w.-]*[~|{}`\\^?=/+*'&%$#!_a-zA-Z0-9-]@"; 00623 00624 if ( localPart[ 0 ] == '\"' || localPart[ localPart.length()-1 ] == '\"' ) { 00625 addrRx = "\"[a-zA-Z@]*[\\w.@-]*[a-zA-Z0-9@]\"@"; 00626 } 00627 if ( domainPart[ 0 ] == '[' || domainPart[ domainPart.length()-1 ] == ']' ) { 00628 addrRx += "\\[[0-9]{,3}(\\.[0-9]{,3}){3}\\]"; 00629 } else { 00630 addrRx += "[\\w-]+(\\.[\\w-]+)*"; 00631 } 00632 QRegExp rx( addrRx ); 00633 return rx.exactMatch( aStr ) && !tooManyAtsFlag; 00634 } 00635 00636 //----------------------------------------------------------------------------- 00637 QString KPIMUtils::simpleEmailAddressErrorMsg() 00638 { 00639 return i18n( "The email address you entered is not valid because it " 00640 "does not seem to contain an actual email address, i.e. " 00641 "something of the form joe@example.org." ); 00642 } 00643 00644 //----------------------------------------------------------------------------- 00645 QByteArray KPIMUtils::extractEmailAddress( const QByteArray &address ) 00646 { 00647 QByteArray dummy1, dummy2, addrSpec; 00648 EmailParseResult result = 00649 splitAddressInternal( address, dummy1, addrSpec, dummy2, 00650 false/* don't allow multiple addresses */ ); 00651 if ( result != AddressOk ) { 00652 addrSpec = QByteArray(); 00653 if ( result != AddressEmpty ) { 00654 kDebug() 00655 << "Input:" << address << "\nError:" 00656 << emailParseResultToString( result ); 00657 } 00658 } 00659 00660 return addrSpec; 00661 } 00662 00663 //----------------------------------------------------------------------------- 00664 QString KPIMUtils::extractEmailAddress( const QString &address ) 00665 { 00666 return QString::fromUtf8( extractEmailAddress( address.toUtf8() ) ); 00667 } 00668 00669 //----------------------------------------------------------------------------- 00670 QByteArray KPIMUtils::firstEmailAddress( const QByteArray &addresses ) 00671 { 00672 QByteArray dummy1, dummy2, addrSpec; 00673 EmailParseResult result = 00674 splitAddressInternal( addresses, dummy1, addrSpec, dummy2, 00675 true/* allow multiple addresses */ ); 00676 if ( result != AddressOk ) { 00677 addrSpec = QByteArray(); 00678 if ( result != AddressEmpty ) { 00679 kDebug() 00680 << "Input: aStr\nError:" 00681 << emailParseResultToString( result ); 00682 } 00683 } 00684 00685 return addrSpec; 00686 } 00687 00688 //----------------------------------------------------------------------------- 00689 QString KPIMUtils::firstEmailAddress( const QString &addresses ) 00690 { 00691 return QString::fromUtf8( firstEmailAddress( addresses.toUtf8() ) ); 00692 } 00693 00694 //----------------------------------------------------------------------------- 00695 bool KPIMUtils::extractEmailAddressAndName( const QString &aStr, 00696 QString &mail, QString &name ) 00697 { 00698 name.clear(); 00699 mail.clear(); 00700 00701 const int len = aStr.length(); 00702 const char cQuotes = '"'; 00703 00704 bool bInComment = false; 00705 bool bInQuotesOutsideOfEmail = false; 00706 int i=0, iAd=0, iMailStart=0, iMailEnd=0; 00707 QChar c; 00708 unsigned int commentstack = 0; 00709 00710 // Find the '@' of the email address 00711 // skipping all '@' inside "(...)" comments: 00712 while ( i < len ) { 00713 c = aStr[i]; 00714 if ( '(' == c ) { 00715 commentstack++; 00716 } 00717 if ( ')' == c ) { 00718 commentstack--; 00719 } 00720 bInComment = commentstack != 0; 00721 if ( '"' == c && !bInComment ) { 00722 bInQuotesOutsideOfEmail = !bInQuotesOutsideOfEmail; 00723 } 00724 00725 if( !bInComment && !bInQuotesOutsideOfEmail ) { 00726 if ( '@' == c ) { 00727 iAd = i; 00728 break; // found it 00729 } 00730 } 00731 ++i; 00732 } 00733 00734 if ( !iAd ) { 00735 // We suppose the user is typing the string manually and just 00736 // has not finished typing the mail address part. 00737 // So we take everything that's left of the '<' as name and the rest as mail 00738 for ( i = 0; len > i; ++i ) { 00739 c = aStr[i]; 00740 if ( '<' != c ) { 00741 name.append( c ); 00742 } else { 00743 break; 00744 } 00745 } 00746 mail = aStr.mid( i + 1 ); 00747 if ( mail.endsWith( '>' ) ) { 00748 mail.truncate( mail.length() - 1 ); 00749 } 00750 00751 } else { 00752 // Loop backwards until we find the start of the string 00753 // or a ',' that is outside of a comment 00754 // and outside of quoted text before the leading '<'. 00755 bInComment = false; 00756 bInQuotesOutsideOfEmail = false; 00757 for ( i = iAd-1; 0 <= i; --i ) { 00758 c = aStr[i]; 00759 if ( bInComment ) { 00760 if ( '(' == c ) { 00761 if ( !name.isEmpty() ) { 00762 name.prepend( ' ' ); 00763 } 00764 bInComment = false; 00765 } else { 00766 name.prepend( c ); // all comment stuff is part of the name 00767 } 00768 } else if ( bInQuotesOutsideOfEmail ) { 00769 if ( cQuotes == c ) { 00770 bInQuotesOutsideOfEmail = false; 00771 } else if ( c != '\\' ) { 00772 name.prepend( c ); 00773 } 00774 } else { 00775 // found the start of this addressee ? 00776 if ( ',' == c ) { 00777 break; 00778 } 00779 // stuff is before the leading '<' ? 00780 if ( iMailStart ) { 00781 if ( cQuotes == c ) { 00782 bInQuotesOutsideOfEmail = true; // end of quoted text found 00783 } else { 00784 name.prepend( c ); 00785 } 00786 } else { 00787 switch ( c.toLatin1() ) { 00788 case '<': 00789 iMailStart = i; 00790 break; 00791 case ')': 00792 if ( !name.isEmpty() ) { 00793 name.prepend( ' ' ); 00794 } 00795 bInComment = true; 00796 break; 00797 default: 00798 if ( ' ' != c ) { 00799 mail.prepend( c ); 00800 } 00801 } 00802 } 00803 } 00804 } 00805 00806 name = name.simplified(); 00807 mail = mail.simplified(); 00808 00809 if ( mail.isEmpty() ) { 00810 return false; 00811 } 00812 00813 mail.append( '@' ); 00814 00815 // Loop forward until we find the end of the string 00816 // or a ',' that is outside of a comment 00817 // and outside of quoted text behind the trailing '>'. 00818 bInComment = false; 00819 bInQuotesOutsideOfEmail = false; 00820 int parenthesesNesting = 0; 00821 for ( i = iAd+1; len > i; ++i ) { 00822 c = aStr[i]; 00823 if ( bInComment ) { 00824 if ( ')' == c ) { 00825 if ( --parenthesesNesting == 0 ) { 00826 bInComment = false; 00827 if ( !name.isEmpty() ) { 00828 name.append( ' ' ); 00829 } 00830 } else { 00831 // nested ")", add it 00832 name.append( ')' ); // name can't be empty here 00833 } 00834 } else { 00835 if ( '(' == c ) { 00836 // nested "(" 00837 ++parenthesesNesting; 00838 } 00839 name.append( c ); // all comment stuff is part of the name 00840 } 00841 } else if ( bInQuotesOutsideOfEmail ) { 00842 if ( cQuotes == c ) { 00843 bInQuotesOutsideOfEmail = false; 00844 } else if ( c != '\\' ) { 00845 name.append( c ); 00846 } 00847 } else { 00848 // found the end of this addressee ? 00849 if ( ',' == c ) { 00850 break; 00851 } 00852 // stuff is behind the trailing '>' ? 00853 if ( iMailEnd ){ 00854 if ( cQuotes == c ) { 00855 bInQuotesOutsideOfEmail = true; // start of quoted text found 00856 } else { 00857 name.append( c ); 00858 } 00859 } else { 00860 switch ( c.toLatin1() ) { 00861 case '>': 00862 iMailEnd = i; 00863 break; 00864 case '(': 00865 if ( !name.isEmpty() ) { 00866 name.append( ' ' ); 00867 } 00868 if ( ++parenthesesNesting > 0 ) { 00869 bInComment = true; 00870 } 00871 break; 00872 default: 00873 if ( ' ' != c ) { 00874 mail.append( c ); 00875 } 00876 } 00877 } 00878 } 00879 } 00880 } 00881 00882 name = name.simplified(); 00883 mail = mail.simplified(); 00884 00885 return ! ( name.isEmpty() || mail.isEmpty() ); 00886 } 00887 00888 //----------------------------------------------------------------------------- 00889 bool KPIMUtils::compareEmail( const QString &email1, const QString &email2, 00890 bool matchName ) 00891 { 00892 QString e1Name, e1Email, e2Name, e2Email; 00893 00894 extractEmailAddressAndName( email1, e1Email, e1Name ); 00895 extractEmailAddressAndName( email2, e2Email, e2Name ); 00896 00897 return e1Email == e2Email && 00898 ( !matchName || ( e1Name == e2Name ) ); 00899 } 00900 00901 //----------------------------------------------------------------------------- 00902 QString KPIMUtils::normalizedAddress( const QString &displayName, 00903 const QString &addrSpec, 00904 const QString &comment ) 00905 { 00906 const QString realDisplayName = KMime::removeBidiControlChars( displayName ); 00907 if ( realDisplayName.isEmpty() && comment.isEmpty() ) { 00908 return addrSpec; 00909 } else if ( comment.isEmpty() ) { 00910 if ( !realDisplayName.startsWith( '\"' ) ) { 00911 return quoteNameIfNecessary( realDisplayName ) + " <" + addrSpec + '>'; 00912 } else { 00913 return realDisplayName + " <" + addrSpec + '>'; 00914 } 00915 } else if ( realDisplayName.isEmpty() ) { 00916 QString commentStr = comment; 00917 return quoteNameIfNecessary( commentStr ) + " <" + addrSpec + '>'; 00918 } else { 00919 return realDisplayName + " (" + comment + ") <" + addrSpec + '>'; 00920 } 00921 } 00922 00923 //----------------------------------------------------------------------------- 00924 QString KPIMUtils::fromIdn( const QString &addrSpec ) 00925 { 00926 const int atPos = addrSpec.lastIndexOf( '@' ); 00927 if ( atPos == -1 ) { 00928 return addrSpec; 00929 } 00930 00931 QString idn = KUrl::fromAce( addrSpec.mid( atPos + 1 ).toLatin1() ); 00932 if ( idn.isEmpty() ) { 00933 return QString(); 00934 } 00935 00936 return addrSpec.left( atPos + 1 ) + idn; 00937 } 00938 00939 //----------------------------------------------------------------------------- 00940 QString KPIMUtils::toIdn( const QString &addrSpec ) 00941 { 00942 const int atPos = addrSpec.lastIndexOf( '@' ); 00943 if ( atPos == -1 ) { 00944 return addrSpec; 00945 } 00946 00947 QString idn = KUrl::toAce( addrSpec.mid( atPos + 1 ) ); 00948 if ( idn.isEmpty() ) { 00949 return addrSpec; 00950 } 00951 00952 return addrSpec.left( atPos + 1 ) + idn; 00953 } 00954 00955 //----------------------------------------------------------------------------- 00956 QString KPIMUtils::normalizeAddressesAndDecodeIdn( const QString &str ) 00957 { 00958 // kDebug() << str; 00959 if ( str.isEmpty() ) { 00960 return str; 00961 } 00962 00963 const QStringList addressList = splitAddressList( str ); 00964 QStringList normalizedAddressList; 00965 00966 QByteArray displayName, addrSpec, comment; 00967 00968 for ( QStringList::ConstIterator it = addressList.begin(); 00969 ( it != addressList.end() ); 00970 ++it ) { 00971 if ( !(*it).isEmpty() ) { 00972 if ( splitAddress( (*it).toUtf8(), 00973 displayName, addrSpec, comment ) == AddressOk ) { 00974 00975 displayName = KMime::decodeRFC2047String(displayName).toUtf8(); 00976 comment = KMime::decodeRFC2047String(comment).toUtf8(); 00977 00978 normalizedAddressList 00979 << normalizedAddress( QString::fromUtf8( displayName ), 00980 fromIdn( QString::fromUtf8( addrSpec ) ), 00981 QString::fromUtf8( comment ) ); 00982 } 00983 } 00984 } 00985 /* 00986 kDebug() << "normalizedAddressList: \"" 00987 << normalizedAddressList.join( ", " ) 00988 << "\""; 00989 */ 00990 return normalizedAddressList.join( ", " ); 00991 } 00992 00993 //----------------------------------------------------------------------------- 00994 QString KPIMUtils::normalizeAddressesAndEncodeIdn( const QString &str ) 00995 { 00996 //kDebug() << str; 00997 if ( str.isEmpty() ) { 00998 return str; 00999 } 01000 01001 const QStringList addressList = splitAddressList( str ); 01002 QStringList normalizedAddressList; 01003 01004 QByteArray displayName, addrSpec, comment; 01005 01006 for ( QStringList::ConstIterator it = addressList.begin(); 01007 ( it != addressList.end() ); 01008 ++it ) { 01009 if ( !(*it).isEmpty() ) { 01010 if ( splitAddress( (*it).toUtf8(), 01011 displayName, addrSpec, comment ) == AddressOk ) { 01012 01013 normalizedAddressList << normalizedAddress( QString::fromUtf8( displayName ), 01014 toIdn( QString::fromUtf8( addrSpec ) ), 01015 QString::fromUtf8( comment ) ); 01016 } 01017 } 01018 } 01019 01020 /* 01021 kDebug() << "normalizedAddressList: \"" 01022 << normalizedAddressList.join( ", " ) 01023 << "\""; 01024 */ 01025 return normalizedAddressList.join( ", " ); 01026 } 01027 01028 //----------------------------------------------------------------------------- 01029 // Escapes unescaped doublequotes in str. 01030 static QString escapeQuotes( const QString &str ) 01031 { 01032 if ( str.isEmpty() ) { 01033 return QString(); 01034 } 01035 01036 QString escaped; 01037 // reserve enough memory for the worst case ( """..."" -> \"\"\"...\"\" ) 01038 escaped.reserve( 2 * str.length() ); 01039 unsigned int len = 0; 01040 for ( int i = 0; i < str.length(); ++i, ++len ) { 01041 if ( str[i] == '"' ) { // unescaped doublequote 01042 escaped[len] = '\\'; 01043 ++len; 01044 } else if ( str[i] == '\\' ) { // escaped character 01045 escaped[len] = '\\'; 01046 ++len; 01047 ++i; 01048 if ( i >= str.length() ) { // handle trailing '\' gracefully 01049 break; 01050 } 01051 } 01052 escaped[len] = str[i]; 01053 } 01054 escaped.truncate( len ); 01055 return escaped; 01056 } 01057 01058 //----------------------------------------------------------------------------- 01059 QString KPIMUtils::quoteNameIfNecessary( const QString &str ) 01060 { 01061 QString quoted = str; 01062 01063 QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ); 01064 // avoid double quoting 01065 if ( ( quoted[0] == '"' ) && ( quoted[quoted.length() - 1] == '"' ) ) { 01066 quoted = "\"" + escapeQuotes( quoted.mid( 1, quoted.length() - 2 ) ) + "\""; 01067 } else if ( quoted.indexOf( needQuotes ) != -1 ) { 01068 quoted = "\"" + escapeQuotes( quoted ) + "\""; 01069 } 01070 01071 return quoted; 01072 } 01073 01074 KUrl KPIMUtils::encodeMailtoUrl( const QString &mailbox ) 01075 { 01076 const QByteArray encodedPath = KMime::encodeRFC2047String( mailbox, "utf-8" ); 01077 KUrl mailtoUrl; 01078 mailtoUrl.setProtocol( "mailto" ); 01079 mailtoUrl.setPath( encodedPath ); 01080 return mailtoUrl; 01081 } 01082 01083 QString KPIMUtils::decodeMailtoUrl( const KUrl &mailtoUrl ) 01084 { 01085 Q_ASSERT( mailtoUrl.protocol().toLower() == "mailto" ); 01086 return KMime::decodeRFC2047String( mailtoUrl.path().toUtf8() ); 01087 }