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