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