PageRenderTime 59ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/kdepim-4.3.4/kmail/stringutil.cpp

#
C++ | 1014 lines | 858 code | 80 blank | 76 comment | 146 complexity | 0c74400eb9d420c1ec6aa1e41ddfff10 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0
  1. /* Copyright 2009 Thomas McGuire <mcguire@kde.org>
  2. This library is free software; you can redistribute it and/or modify
  3. it under the terms of the GNU Library General Public License as published
  4. by the Free Software Foundation; either version 2 of the License or
  5. ( at your option ) version 3 or, at the discretion of KDE e.V.
  6. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Library General Public License for more details.
  11. You should have received a copy of the GNU Library General Public License
  12. along with this library; see the file COPYING.LIB. If not, write to
  13. the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  14. Boston, MA 02110-1301, USA.
  15. */
  16. #include "stringutil.h"
  17. #ifndef KMAIL_UNITTESTS
  18. #include "kmaddrbook.h"
  19. #include "kmkernel.h"
  20. #include <libkdepim/kaddrbookexternal.h>
  21. #include <mimelib/enum.h>
  22. #include <kmime/kmime_charfreq.h>
  23. #include <kmime/kmime_header_parsing.h>
  24. #include <KPIMUtils/Email>
  25. #include <KPIMIdentities/IdentityManager>
  26. #include <kascii.h>
  27. #include <KConfigGroup>
  28. #include <KDebug>
  29. #include <KUrl>
  30. #include <kuser.h>
  31. #include <QHostInfo>
  32. #include <QRegExp>
  33. #endif
  34. #include <QStringList>
  35. #ifndef KMAIL_UNITTESTS
  36. using namespace KMime;
  37. using namespace KMime::Types;
  38. using namespace KMime::HeaderParsing;
  39. #endif
  40. namespace KMail
  41. {
  42. namespace StringUtil
  43. {
  44. static void removeTrailingSpace( QString &line )
  45. {
  46. int i = line.length()-1;
  47. while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t')))
  48. i--;
  49. line.truncate( i+1);
  50. }
  51. static QString splitLine( QString &line)
  52. {
  53. removeTrailingSpace( line );
  54. int i = 0;
  55. int j = -1;
  56. int l = line.length();
  57. // TODO: Replace tabs with spaces first.
  58. while(i < l)
  59. {
  60. QChar c = line[i];
  61. if ((c == '>') || (c == ':') || (c == '|'))
  62. j = i+1;
  63. else if ((c != ' ') && (c != '\t'))
  64. break;
  65. i++;
  66. }
  67. if ( j <= 0 )
  68. {
  69. return "";
  70. }
  71. if ( i == l )
  72. {
  73. QString result = line.left(j);
  74. line.clear();
  75. return result;
  76. }
  77. QString result = line.left(j);
  78. line = line.mid(j);
  79. return result;
  80. }
  81. static QString flowText(QString &text, const QString& indent, int maxLength)
  82. {
  83. maxLength--;
  84. if (text.isEmpty())
  85. {
  86. return indent+"<NULL>\n";
  87. }
  88. QString result;
  89. while (1)
  90. {
  91. int i;
  92. if ((int) text.length() > maxLength)
  93. {
  94. i = maxLength;
  95. while( (i >= 0) && (text[i] != ' '))
  96. i--;
  97. if (i <= 0)
  98. {
  99. // Couldn't break before maxLength.
  100. i = maxLength;
  101. // while( (i < (int) text.length()) && (text[i] != ' '))
  102. // i++;
  103. }
  104. }
  105. else
  106. {
  107. i = text.length();
  108. }
  109. QString line = text.left(i);
  110. if (i < (int) text.length())
  111. text = text.mid(i);
  112. else
  113. text.clear();
  114. result += indent + line + '\n';
  115. if (text.isEmpty())
  116. return result;
  117. }
  118. }
  119. static bool flushPart(QString &msg, QStringList &part,
  120. const QString &indent, int maxLength)
  121. {
  122. maxLength -= indent.length();
  123. if (maxLength < 20) maxLength = 20;
  124. // Remove empty lines at end of quote
  125. while ((part.begin() != part.end()) && part.last().isEmpty())
  126. {
  127. part.removeLast();
  128. }
  129. QString text;
  130. for(QStringList::Iterator it2 = part.begin();
  131. it2 != part.end();
  132. ++it2)
  133. {
  134. QString line = (*it2);
  135. if (line.isEmpty())
  136. {
  137. if (!text.isEmpty())
  138. msg += flowText(text, indent, maxLength);
  139. msg += indent + '\n';
  140. }
  141. else
  142. {
  143. if (text.isEmpty())
  144. text = line;
  145. else
  146. text += ' '+line.trimmed();
  147. if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10)))
  148. msg += flowText(text, indent, maxLength);
  149. }
  150. }
  151. if (!text.isEmpty())
  152. msg += flowText(text, indent, maxLength);
  153. bool appendEmptyLine = true;
  154. if (!part.count())
  155. appendEmptyLine = false;
  156. part.clear();
  157. return appendEmptyLine;
  158. }
  159. QString stripSignature ( const QString & msg, bool clearSigned )
  160. {
  161. // Following RFC 3676, only > before --
  162. // I prefer to not delete a SB instead of delete good mail content.
  163. const QRegExp sbDelimiterSearch = clearSigned ?
  164. QRegExp( "(^|\n)[> ]*--\\s?\n" ) : QRegExp( "(^|\n)[> ]*-- \n" );
  165. // The regular expression to look for prefix change
  166. const QRegExp commonReplySearch = QRegExp( "^[ ]*>" );
  167. QString res = msg;
  168. int posDeletingStart = 1; // to start looking at 0
  169. // While there are SB delimiters (start looking just before the deleted SB)
  170. while ( ( posDeletingStart = res.indexOf( sbDelimiterSearch , posDeletingStart -1 ) ) >= 0 )
  171. {
  172. QString prefix; // the current prefix
  173. QString line; // the line to check if is part of the SB
  174. int posNewLine = -1;
  175. int posSignatureBlock = -1;
  176. // Look for the SB beginning
  177. posSignatureBlock = res.indexOf( '-', posDeletingStart );
  178. // The prefix before "-- "$
  179. if ( res[posDeletingStart] == '\n' ) ++posDeletingStart;
  180. prefix = res.mid( posDeletingStart, posSignatureBlock - posDeletingStart );
  181. posNewLine = res.indexOf( '\n', posSignatureBlock ) + 1;
  182. // now go to the end of the SB
  183. while ( posNewLine < res.size() && posNewLine > 0 )
  184. {
  185. // handle the undefined case for mid ( x , -n ) where n>1
  186. int nextPosNewLine = res.indexOf( '\n', posNewLine );
  187. if ( nextPosNewLine < 0 ) nextPosNewLine = posNewLine - 1;
  188. line = res.mid( posNewLine, nextPosNewLine - posNewLine );
  189. // check when the SB ends:
  190. // * does not starts with prefix or
  191. // * starts with prefix+(any substring of prefix)
  192. if ( ( prefix.isEmpty() && line.indexOf( commonReplySearch ) < 0 ) ||
  193. ( !prefix.isEmpty() && line.startsWith( prefix ) &&
  194. line.mid( prefix.size() ).indexOf( commonReplySearch ) < 0 ) )
  195. {
  196. posNewLine = res.indexOf( '\n', posNewLine ) + 1;
  197. }
  198. else
  199. break; // end of the SB
  200. }
  201. // remove the SB or truncate when is the last SB
  202. if ( posNewLine > 0 )
  203. res.remove( posDeletingStart, posNewLine - posDeletingStart );
  204. else
  205. res.truncate( posDeletingStart );
  206. }
  207. return res;
  208. }
  209. #ifndef KMAIL_UNITTESTS
  210. //-----------------------------------------------------------------------------
  211. QList<int> determineAllowedCtes( const CharFreq& cf,
  212. bool allow8Bit,
  213. bool willBeSigned )
  214. {
  215. QList<int> allowedCtes;
  216. switch ( cf.type() ) {
  217. case CharFreq::SevenBitText:
  218. allowedCtes << DwMime::kCte7bit;
  219. case CharFreq::EightBitText:
  220. if ( allow8Bit )
  221. allowedCtes << DwMime::kCte8bit;
  222. case CharFreq::SevenBitData:
  223. if ( cf.printableRatio() > 5.0/6.0 ) {
  224. // let n the length of data and p the number of printable chars.
  225. // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
  226. // => qp < base64 iff p > 5n/6.
  227. allowedCtes << DwMime::kCteQp;
  228. allowedCtes << DwMime::kCteBase64;
  229. } else {
  230. allowedCtes << DwMime::kCteBase64;
  231. allowedCtes << DwMime::kCteQp;
  232. }
  233. break;
  234. case CharFreq::EightBitData:
  235. allowedCtes << DwMime::kCteBase64;
  236. break;
  237. case CharFreq::None:
  238. default:
  239. // just nothing (avoid compiler warning)
  240. ;
  241. }
  242. // In the following cases only QP and Base64 are allowed:
  243. // - the buffer will be OpenPGP/MIME signed and it contains trailing
  244. // whitespace (cf. RFC 3156)
  245. // - a line starts with "From "
  246. if ( ( willBeSigned && cf.hasTrailingWhitespace() ) ||
  247. cf.hasLeadingFrom() ) {
  248. allowedCtes.removeAll( DwMime::kCte8bit );
  249. allowedCtes.removeAll( DwMime::kCte7bit );
  250. }
  251. return allowedCtes;
  252. }
  253. AddressList splitAddrField( const QByteArray & str )
  254. {
  255. AddressList result;
  256. const char * scursor = str.begin();
  257. if ( !scursor )
  258. return AddressList();
  259. const char * const send = str.begin() + str.length();
  260. if ( !parseAddressList( scursor, send, result ) )
  261. kDebug() << "Error in address splitting: parseAddressList returned false!";
  262. return result;
  263. }
  264. QString generateMessageId( const QString& addr )
  265. {
  266. QDateTime datetime = QDateTime::currentDateTime();
  267. QString msgIdStr;
  268. msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" );
  269. QString msgIdSuffix;
  270. KConfigGroup general( KMKernel::config(), "General" );
  271. if( general.readEntry( "useCustomMessageIdSuffix", false ) )
  272. msgIdSuffix = general.readEntry( "myMessageIdSuffix" );
  273. if( !msgIdSuffix.isEmpty() )
  274. msgIdStr += '@' + msgIdSuffix;
  275. else
  276. msgIdStr += '.' + KPIMUtils::toIdn( addr );
  277. msgIdStr += '>';
  278. return msgIdStr;
  279. }
  280. #endif
  281. QByteArray html2source( const QByteArray & src )
  282. {
  283. QByteArray result( 1 + 6*src.length(), '\0' ); // maximal possible length
  284. QByteArray::ConstIterator s = src.begin();
  285. QByteArray::Iterator d = result.begin();
  286. while ( *s ) {
  287. switch ( *s ) {
  288. case '<': {
  289. *d++ = '&';
  290. *d++ = 'l';
  291. *d++ = 't';
  292. *d++ = ';';
  293. ++s;
  294. }
  295. break;
  296. case '\r': {
  297. ++s;
  298. }
  299. break;
  300. case '\n': {
  301. *d++ = '<';
  302. *d++ = 'b';
  303. *d++ = 'r';
  304. *d++ = '>';
  305. ++s;
  306. }
  307. break;
  308. case '>': {
  309. *d++ = '&';
  310. *d++ = 'g';
  311. *d++ = 't';
  312. *d++ = ';';
  313. ++s;
  314. }
  315. break;
  316. case '&': {
  317. *d++ = '&';
  318. *d++ = 'a';
  319. *d++ = 'm';
  320. *d++ = 'p';
  321. *d++ = ';';
  322. ++s;
  323. }
  324. break;
  325. case '"': {
  326. *d++ = '&';
  327. *d++ = 'q';
  328. *d++ = 'u';
  329. *d++ = 'o';
  330. *d++ = 't';
  331. *d++ = ';';
  332. ++s;
  333. }
  334. break;
  335. case '\'': {
  336. *d++ = '&';
  337. *d++ = 'a';
  338. *d++ = 'p';
  339. *d++ = 's';
  340. *d++ = ';';
  341. ++s;
  342. }
  343. break;
  344. default:
  345. *d++ = *s++;
  346. }
  347. }
  348. result.truncate( d - result.begin() );
  349. return result;
  350. }
  351. #ifndef KMAIL_UNITTESTS
  352. QString encodeMailtoUrl( const QString& str )
  353. {
  354. QString result;
  355. result = QString::fromLatin1( KMMsgBase::encodeRFC2047String( str,
  356. "utf-8" ) );
  357. result = KUrl::toPercentEncoding( result );
  358. return result;
  359. }
  360. QString decodeMailtoUrl( const QString& url )
  361. {
  362. QString result;
  363. result = KUrl::fromPercentEncoding( url.toLatin1() );
  364. result = KMMsgBase::decodeRFC2047String( result.toLatin1() );
  365. return result;
  366. }
  367. #endif
  368. QByteArray stripEmailAddr( const QByteArray& aStr )
  369. {
  370. //kDebug() << "(" << aStr <<" )";
  371. if ( aStr.isEmpty() )
  372. return QByteArray();
  373. QByteArray result;
  374. // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
  375. // The purpose is to extract a displayable string from the mailboxes.
  376. // Comments in the addr-spec are not handled. No error checking is done.
  377. QByteArray name;
  378. QByteArray comment;
  379. QByteArray angleAddress;
  380. enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
  381. bool inQuotedString = false;
  382. int commentLevel = 0;
  383. for ( const char* p = aStr.data(); *p; ++p ) {
  384. switch ( context ) {
  385. case TopLevel : {
  386. switch ( *p ) {
  387. case '"' : inQuotedString = !inQuotedString;
  388. break;
  389. case '(' : if ( !inQuotedString ) {
  390. context = InComment;
  391. commentLevel = 1;
  392. }
  393. else
  394. name += *p;
  395. break;
  396. case '<' : if ( !inQuotedString ) {
  397. context = InAngleAddress;
  398. }
  399. else
  400. name += *p;
  401. break;
  402. case '\\' : // quoted character
  403. ++p; // skip the '\'
  404. if ( *p )
  405. name += *p;
  406. break;
  407. case ',' : if ( !inQuotedString ) {
  408. // next email address
  409. if ( !result.isEmpty() )
  410. result += ", ";
  411. name = name.trimmed();
  412. comment = comment.trimmed();
  413. angleAddress = angleAddress.trimmed();
  414. /*
  415. kDebug() <<"Name : \"" << name
  416. << "\"";
  417. kDebug() <<"Comment : \"" << comment
  418. << "\"";
  419. kDebug() <<"Address : \"" << angleAddress
  420. << "\"";
  421. */
  422. if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
  423. // handle Outlook-style addresses like
  424. // john.doe@invalid (John Doe)
  425. result += comment;
  426. }
  427. else if ( !name.isEmpty() ) {
  428. result += name;
  429. }
  430. else if ( !comment.isEmpty() ) {
  431. result += comment;
  432. }
  433. else if ( !angleAddress.isEmpty() ) {
  434. result += angleAddress;
  435. }
  436. name = QByteArray();
  437. comment = QByteArray();
  438. angleAddress = QByteArray();
  439. }
  440. else
  441. name += *p;
  442. break;
  443. default : name += *p;
  444. }
  445. break;
  446. }
  447. case InComment : {
  448. switch ( *p ) {
  449. case '(' : ++commentLevel;
  450. comment += *p;
  451. break;
  452. case ')' : --commentLevel;
  453. if ( commentLevel == 0 ) {
  454. context = TopLevel;
  455. comment += ' '; // separate the text of several comments
  456. }
  457. else
  458. comment += *p;
  459. break;
  460. case '\\' : // quoted character
  461. ++p; // skip the '\'
  462. if ( *p )
  463. comment += *p;
  464. break;
  465. default : comment += *p;
  466. }
  467. break;
  468. }
  469. case InAngleAddress : {
  470. switch ( *p ) {
  471. case '"' : inQuotedString = !inQuotedString;
  472. angleAddress += *p;
  473. break;
  474. case '>' : if ( !inQuotedString ) {
  475. context = TopLevel;
  476. }
  477. else
  478. angleAddress += *p;
  479. break;
  480. case '\\' : // quoted character
  481. ++p; // skip the '\'
  482. if ( *p )
  483. angleAddress += *p;
  484. break;
  485. default : angleAddress += *p;
  486. }
  487. break;
  488. }
  489. } // switch ( context )
  490. }
  491. if ( !result.isEmpty() )
  492. result += ", ";
  493. name = name.trimmed();
  494. comment = comment.trimmed();
  495. angleAddress = angleAddress.trimmed();
  496. /*
  497. kDebug() <<"Name : \"" << name <<"\"";
  498. kDebug() <<"Comment : \"" << comment <<"\"";
  499. kDebug() <<"Address : \"" << angleAddress <<"\"";
  500. */
  501. if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
  502. // handle Outlook-style addresses like
  503. // john.doe@invalid (John Doe)
  504. result += comment;
  505. }
  506. else if ( !name.isEmpty() ) {
  507. result += name;
  508. }
  509. else if ( !comment.isEmpty() ) {
  510. result += comment;
  511. }
  512. else if ( !angleAddress.isEmpty() ) {
  513. result += angleAddress;
  514. }
  515. //kDebug() << "Returns \"" << result << "\"";
  516. return result;
  517. }
  518. QString stripEmailAddr( const QString& aStr )
  519. {
  520. //kDebug() << "(" << aStr << ")";
  521. if ( aStr.isEmpty() )
  522. return QString();
  523. QString result;
  524. // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
  525. // The purpose is to extract a displayable string from the mailboxes.
  526. // Comments in the addr-spec are not handled. No error checking is done.
  527. QString name;
  528. QString comment;
  529. QString angleAddress;
  530. enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
  531. bool inQuotedString = false;
  532. int commentLevel = 0;
  533. QChar ch;
  534. int strLength(aStr.length());
  535. for ( int index = 0; index < strLength; ++index ) {
  536. ch = aStr[index];
  537. switch ( context ) {
  538. case TopLevel : {
  539. switch ( ch.toLatin1() ) {
  540. case '"' : inQuotedString = !inQuotedString;
  541. break;
  542. case '(' : if ( !inQuotedString ) {
  543. context = InComment;
  544. commentLevel = 1;
  545. }
  546. else
  547. name += ch;
  548. break;
  549. case '<' : if ( !inQuotedString ) {
  550. context = InAngleAddress;
  551. }
  552. else
  553. name += ch;
  554. break;
  555. case '\\' : // quoted character
  556. ++index; // skip the '\'
  557. if ( index < aStr.length() )
  558. name += aStr[index];
  559. break;
  560. case ',' : if ( !inQuotedString ) {
  561. // next email address
  562. if ( !result.isEmpty() )
  563. result += ", ";
  564. name = name.trimmed();
  565. comment = comment.trimmed();
  566. angleAddress = angleAddress.trimmed();
  567. /*
  568. kDebug() <<"Name : \"" << name
  569. << "\"";
  570. kDebug() <<"Comment : \"" << comment
  571. << "\"";
  572. kDebug() <<"Address : \"" << angleAddress
  573. << "\"";
  574. */
  575. if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
  576. // handle Outlook-style addresses like
  577. // john.doe@invalid (John Doe)
  578. result += comment;
  579. }
  580. else if ( !name.isEmpty() ) {
  581. result += name;
  582. }
  583. else if ( !comment.isEmpty() ) {
  584. result += comment;
  585. }
  586. else if ( !angleAddress.isEmpty() ) {
  587. result += angleAddress;
  588. }
  589. name.clear();
  590. comment.clear();
  591. angleAddress.clear();
  592. }
  593. else
  594. name += ch;
  595. break;
  596. default : name += ch;
  597. }
  598. break;
  599. }
  600. case InComment : {
  601. switch ( ch.toLatin1() ) {
  602. case '(' : ++commentLevel;
  603. comment += ch;
  604. break;
  605. case ')' : --commentLevel;
  606. if ( commentLevel == 0 ) {
  607. context = TopLevel;
  608. comment += ' '; // separate the text of several comments
  609. }
  610. else
  611. comment += ch;
  612. break;
  613. case '\\' : // quoted character
  614. ++index; // skip the '\'
  615. if ( index < aStr.length() )
  616. comment += aStr[index];
  617. break;
  618. default : comment += ch;
  619. }
  620. break;
  621. }
  622. case InAngleAddress : {
  623. switch ( ch.toLatin1() ) {
  624. case '"' : inQuotedString = !inQuotedString;
  625. angleAddress += ch;
  626. break;
  627. case '>' : if ( !inQuotedString ) {
  628. context = TopLevel;
  629. }
  630. else
  631. angleAddress += ch;
  632. break;
  633. case '\\' : // quoted character
  634. ++index; // skip the '\'
  635. if ( index < aStr.length() )
  636. angleAddress += aStr[index];
  637. break;
  638. default : angleAddress += ch;
  639. }
  640. break;
  641. }
  642. } // switch ( context )
  643. }
  644. if ( !result.isEmpty() )
  645. result += ", ";
  646. name = name.trimmed();
  647. comment = comment.trimmed();
  648. angleAddress = angleAddress.trimmed();
  649. /*
  650. kDebug() <<"Name : \"" << name <<"\"";
  651. kDebug() <<"Comment : \"" << comment <<"\"";
  652. kDebug() <<"Address : \"" << angleAddress <<"\"";
  653. */
  654. if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
  655. // handle Outlook-style addresses like
  656. // john.doe@invalid (John Doe)
  657. result += comment;
  658. }
  659. else if ( !name.isEmpty() ) {
  660. result += name;
  661. }
  662. else if ( !comment.isEmpty() ) {
  663. result += comment;
  664. }
  665. else if ( !angleAddress.isEmpty() ) {
  666. result += angleAddress;
  667. }
  668. //kDebug() << "Returns \"" << result << "\"";
  669. return result;
  670. }
  671. QString quoteHtmlChars( const QString& str, bool removeLineBreaks )
  672. {
  673. QString result;
  674. unsigned int strLength(str.length());
  675. result.reserve( 6*strLength ); // maximal possible length
  676. for( unsigned int i = 0; i < strLength; ++i ) {
  677. switch ( str[i].toLatin1() ) {
  678. case '<':
  679. result += "&lt;";
  680. break;
  681. case '>':
  682. result += "&gt;";
  683. break;
  684. case '&':
  685. result += "&amp;";
  686. break;
  687. case '"':
  688. result += "&quot;";
  689. break;
  690. case '\n':
  691. if ( !removeLineBreaks )
  692. result += "<br>";
  693. break;
  694. case '\r':
  695. // ignore CR
  696. break;
  697. default:
  698. result += str[i];
  699. }
  700. }
  701. result.squeeze();
  702. return result;
  703. }
  704. #ifndef KMAIL_UNITTESTS
  705. QString emailAddrAsAnchor( const QString& aEmail, bool stripped, const QString& cssStyle,
  706. bool aLink )
  707. {
  708. if( aEmail.isEmpty() )
  709. return aEmail;
  710. const QStringList addressList = KPIMUtils::splitAddressList( aEmail );
  711. QString result;
  712. for( QStringList::ConstIterator it = addressList.constBegin();
  713. ( it != addressList.constEnd() );
  714. ++it ) {
  715. if( !(*it).isEmpty() ) {
  716. QString address = *it;
  717. if( aLink ) {
  718. result += "<a href=\"mailto:"
  719. + encodeMailtoUrl( address )
  720. + "\" "+cssStyle+">";
  721. }
  722. if( stripped )
  723. address = stripEmailAddr( address );
  724. result += quoteHtmlChars( address, true );
  725. if( aLink ) {
  726. result += "</a>, ";
  727. }
  728. }
  729. }
  730. // cut of the trailing ", "
  731. if( aLink ) {
  732. result.truncate( result.length() - 2 );
  733. }
  734. //kDebug() << "('" << aEmail << "') returns:\n-->" << result << "<--";
  735. return result;
  736. }
  737. QStringList stripAddressFromAddressList( const QString& address,
  738. const QStringList& list )
  739. {
  740. QStringList addresses( list );
  741. QString addrSpec( KPIMUtils::extractEmailAddress( address ) );
  742. for ( QStringList::Iterator it = addresses.begin();
  743. it != addresses.end(); ) {
  744. if ( kasciistricmp( addrSpec.toUtf8().data(),
  745. KPIMUtils::extractEmailAddress( *it ).toUtf8().data() ) == 0 ) {
  746. kDebug() << "Removing" << *it <<" from the address list";
  747. it = addresses.erase( it );
  748. }
  749. else
  750. ++it;
  751. }
  752. return addresses;
  753. }
  754. QStringList stripMyAddressesFromAddressList( const QStringList& list )
  755. {
  756. QStringList addresses = list;
  757. for( QStringList::Iterator it = addresses.begin();
  758. it != addresses.end(); ) {
  759. kDebug() << "Check whether" << *it <<"is one of my addresses";
  760. if( kmkernel->identityManager()->thatIsMe( KPIMUtils::extractEmailAddress( *it ) ) ) {
  761. kDebug() << "Removing" << *it <<"from the address list";
  762. it = addresses.erase( it );
  763. }
  764. else
  765. ++it;
  766. }
  767. return addresses;
  768. }
  769. bool addressIsInAddressList( const QString& address,
  770. const QStringList& addresses )
  771. {
  772. QString addrSpec = KPIMUtils::extractEmailAddress( address );
  773. for( QStringList::ConstIterator it = addresses.begin();
  774. it != addresses.end(); ++it ) {
  775. if ( kasciistricmp( addrSpec.toUtf8().data(),
  776. KPIMUtils::extractEmailAddress( *it ).toUtf8().data() ) == 0 )
  777. return true;
  778. }
  779. return false;
  780. }
  781. QString expandAliases( const QString& recipients )
  782. {
  783. if ( recipients.isEmpty() )
  784. return QString();
  785. QStringList recipientList = KPIMUtils::splitAddressList( recipients );
  786. QString expandedRecipients;
  787. for ( QStringList::Iterator it = recipientList.begin();
  788. it != recipientList.end(); ++it ) {
  789. if ( !expandedRecipients.isEmpty() )
  790. expandedRecipients += ", ";
  791. QString receiver = (*it).trimmed();
  792. // try to expand distribution list
  793. QString expandedList = KPIM::KAddrBookExternal::expandDistributionList( receiver );
  794. if ( !expandedList.isEmpty() ) {
  795. expandedRecipients += expandedList;
  796. continue;
  797. }
  798. // try to expand nick name
  799. QString expandedNickName = KabcBridge::expandNickName( receiver );
  800. if ( !expandedNickName.isEmpty() ) {
  801. expandedRecipients += expandedNickName;
  802. continue;
  803. }
  804. // check whether the address is missing the domain part
  805. QByteArray displayName, addrSpec, comment;
  806. KPIMUtils::splitAddress( receiver.toLatin1(), displayName, addrSpec, comment );
  807. if ( !addrSpec.contains('@') ) {
  808. KConfigGroup general( KMKernel::config(), "General" );
  809. QString defaultdomain = general.readEntry( "Default domain" );
  810. if ( !defaultdomain.isEmpty() ) {
  811. expandedRecipients += KPIMUtils::normalizedAddress( displayName, addrSpec + '@' + defaultdomain, comment );
  812. }
  813. else {
  814. expandedRecipients += guessEmailAddressFromLoginName( addrSpec );
  815. }
  816. }
  817. else
  818. expandedRecipients += receiver;
  819. }
  820. return expandedRecipients;
  821. }
  822. QString guessEmailAddressFromLoginName( const QString& loginName )
  823. {
  824. if ( loginName.isEmpty() )
  825. return QString();
  826. QString address = loginName;
  827. address += '@';
  828. address += QHostInfo::localHostName();
  829. // try to determine the real name
  830. const KUser user( loginName );
  831. if ( user.isValid() ) {
  832. QString fullName = user.property( KUser::FullName ).toString();
  833. if ( fullName.contains( QRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) )
  834. address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" )
  835. + "\" <" + address + '>';
  836. else
  837. address = fullName + " <" + address + '>';
  838. }
  839. return address;
  840. }
  841. #endif
  842. QString smartQuote( const QString & msg, int maxLineLength )
  843. {
  844. QStringList part;
  845. QString oldIndent;
  846. bool firstPart = true;
  847. const QStringList lines = msg.split('\n');
  848. QString result;
  849. for(QStringList::const_iterator it = lines.begin();
  850. it != lines.end();
  851. ++it)
  852. {
  853. QString line = *it;
  854. const QString indent = splitLine( line );
  855. if ( line.isEmpty())
  856. {
  857. if (!firstPart)
  858. part.append(QString());
  859. continue;
  860. };
  861. if (firstPart)
  862. {
  863. oldIndent = indent;
  864. firstPart = false;
  865. }
  866. if (oldIndent != indent)
  867. {
  868. QString fromLine;
  869. // Search if the last non-blank line could be "From" line
  870. if (part.count() && (oldIndent.length() < indent.length()))
  871. {
  872. QStringList::Iterator it2 = part.isEmpty() ? part.end() : --part.end();
  873. // FIXME: what if all strings are empty? Then we'll decrement part.begin().
  874. // Shouldn't we also check for .begin()?
  875. while( (it2 != part.end()) && (*it2).isEmpty())
  876. --it2;
  877. if ((it2 != part.end()) && ((*it2).endsWith(':')))
  878. {
  879. fromLine = oldIndent + (*it2) + '\n';
  880. part.erase(it2);
  881. }
  882. }
  883. if (flushPart( result, part, oldIndent, maxLineLength))
  884. {
  885. if (oldIndent.length() > indent.length())
  886. result += indent + '\n';
  887. else
  888. result += oldIndent + '\n';
  889. }
  890. if (!fromLine.isEmpty())
  891. {
  892. result += fromLine;
  893. }
  894. oldIndent = indent;
  895. }
  896. part.append(line);
  897. }
  898. flushPart( result, part, oldIndent, maxLineLength);
  899. return result;
  900. }
  901. #ifndef KMAIL_UNITTESTS
  902. QMap<QString, QString> parseMailtoUrl ( const KUrl& url )
  903. {
  904. kDebug() << url.pathOrUrl();
  905. QMap<QString, QString> values = url.queryItems( KUrl::CaseInsensitiveKeys );
  906. QString to = decodeMailtoUrl( url.path() );
  907. to = to.isEmpty() ? values.value( "to" ) : to + QString( ", " ) + values.value( "to" );
  908. values.insert( "to", to );
  909. return values;
  910. }
  911. #endif
  912. }
  913. }