You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1073 lines
28 KiB
1073 lines
28 KiB
/* Copyright 2009 Thomas McGuire <mcguire@kde.org> |
|
|
|
This program is free software; you can redistribute it and/or |
|
modify it under the terms of the GNU General Public License as |
|
published by the Free Software Foundation; either version 2 of |
|
the License or (at your option) version 3 or any later version |
|
accepted by the membership of KDE e.V. (or its successor approved |
|
by the membership of KDE e.V.), which shall act as a proxy |
|
defined in Section 14 of version 3 of the license. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
#include "stringutil.h" |
|
|
|
#ifndef KMAIL_UNITTESTS |
|
|
|
#include "kmaddrbook.h" |
|
#include "kmkernel.h" |
|
|
|
#include <libkdepim/kaddrbookexternal.h> |
|
#include <mimelib/enum.h> |
|
|
|
#include <kmime/kmime_charfreq.h> |
|
#include <kmime/kmime_header_parsing.h> |
|
#include <KPIMUtils/Email> |
|
#include <KPIMIdentities/IdentityManager> |
|
|
|
#include <kascii.h> |
|
#include <KConfigGroup> |
|
#include <KDebug> |
|
#include <KUrl> |
|
#include <kuser.h> |
|
|
|
#include <QHostInfo> |
|
#include <QRegExp> |
|
#endif |
|
#include <QStringList> |
|
|
|
#ifndef KMAIL_UNITTESTS |
|
|
|
using namespace KMime; |
|
using namespace KMime::Types; |
|
using namespace KMime::HeaderParsing; |
|
|
|
#endif |
|
|
|
namespace KMail |
|
{ |
|
|
|
namespace StringUtil |
|
{ |
|
|
|
static void removeTrailingSpace( QString &line ) |
|
{ |
|
int i = line.length()-1; |
|
while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t'))) |
|
i--; |
|
line.truncate( i+1); |
|
} |
|
|
|
static QString splitLine( QString &line) |
|
{ |
|
removeTrailingSpace( line ); |
|
int i = 0; |
|
int j = -1; |
|
int l = line.length(); |
|
|
|
// TODO: Replace tabs with spaces first. |
|
|
|
while(i < l) |
|
{ |
|
QChar c = line[i]; |
|
if ((c == '>') || (c == ':') || (c == '|')) |
|
j = i+1; |
|
else if ((c != ' ') && (c != '\t')) |
|
break; |
|
i++; |
|
} |
|
|
|
if ( j <= 0 ) |
|
{ |
|
return ""; |
|
} |
|
if ( i == l ) |
|
{ |
|
QString result = line.left(j); |
|
line.clear(); |
|
return result; |
|
} |
|
|
|
QString result = line.left(j); |
|
line = line.mid(j); |
|
return result; |
|
} |
|
|
|
|
|
static QString flowText(QString &text, const QString& indent, int maxLength) |
|
{ |
|
maxLength--; |
|
if (text.isEmpty()) |
|
{ |
|
return indent+"<NULL>\n"; |
|
} |
|
QString result; |
|
while (1) |
|
{ |
|
int i; |
|
if ((int) text.length() > maxLength) |
|
{ |
|
i = maxLength; |
|
while( (i >= 0) && (text[i] != ' ')) |
|
i--; |
|
if (i <= 0) |
|
{ |
|
// Couldn't break before maxLength. |
|
i = maxLength; |
|
// while( (i < (int) text.length()) && (text[i] != ' ')) |
|
// i++; |
|
} |
|
} |
|
else |
|
{ |
|
i = text.length(); |
|
} |
|
|
|
QString line = text.left(i); |
|
if (i < (int) text.length()) |
|
text = text.mid(i); |
|
else |
|
text.clear(); |
|
|
|
result += indent + line + '\n'; |
|
|
|
if (text.isEmpty()) |
|
return result; |
|
} |
|
} |
|
|
|
static bool flushPart(QString &msg, QStringList &part, |
|
const QString &indent, int maxLength) |
|
{ |
|
maxLength -= indent.length(); |
|
if (maxLength < 20) maxLength = 20; |
|
|
|
// Remove empty lines at end of quote |
|
while ((part.begin() != part.end()) && part.last().isEmpty()) |
|
{ |
|
part.removeLast(); |
|
} |
|
|
|
QString text; |
|
for(QStringList::Iterator it2 = part.begin(); |
|
it2 != part.end(); |
|
++it2) |
|
{ |
|
QString line = (*it2); |
|
|
|
if (line.isEmpty()) |
|
{ |
|
if (!text.isEmpty()) |
|
msg += flowText(text, indent, maxLength); |
|
msg += indent + '\n'; |
|
} |
|
else |
|
{ |
|
if (text.isEmpty()) |
|
text = line; |
|
else |
|
text += ' '+line.trimmed(); |
|
|
|
if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10))) |
|
msg += flowText(text, indent, maxLength); |
|
} |
|
} |
|
if (!text.isEmpty()) |
|
msg += flowText(text, indent, maxLength); |
|
|
|
bool appendEmptyLine = true; |
|
if (!part.count()) |
|
appendEmptyLine = false; |
|
|
|
part.clear(); |
|
return appendEmptyLine; |
|
} |
|
|
|
QString stripSignature ( const QString & msg, bool clearSigned ) |
|
{ |
|
// Following RFC 3676, only > before -- |
|
// I prefer to not delete a SB instead of delete good mail content. |
|
const QRegExp sbDelimiterSearch = clearSigned ? |
|
QRegExp( "(^|\n)[> ]*--\\s?\n" ) : QRegExp( "(^|\n)[> ]*-- \n" ); |
|
// The regular expression to look for prefix change |
|
const QRegExp commonReplySearch = QRegExp( "^[ ]*>" ); |
|
|
|
QString res = msg; |
|
int posDeletingStart = 1; // to start looking at 0 |
|
|
|
// While there are SB delimiters (start looking just before the deleted SB) |
|
while ( ( posDeletingStart = res.indexOf( sbDelimiterSearch , posDeletingStart -1 ) ) >= 0 ) |
|
{ |
|
QString prefix; // the current prefix |
|
QString line; // the line to check if is part of the SB |
|
int posNewLine = -1; |
|
int posSignatureBlock = -1; |
|
// Look for the SB beginning |
|
posSignatureBlock = res.indexOf( '-', posDeletingStart ); |
|
// The prefix before "-- "$ |
|
if ( res[posDeletingStart] == '\n' ) ++posDeletingStart; |
|
prefix = res.mid( posDeletingStart, posSignatureBlock - posDeletingStart ); |
|
posNewLine = res.indexOf( '\n', posSignatureBlock ) + 1; |
|
|
|
// now go to the end of the SB |
|
while ( posNewLine < res.size() && posNewLine > 0 ) |
|
{ |
|
// handle the undefined case for mid ( x , -n ) where n>1 |
|
int nextPosNewLine = res.indexOf( '\n', posNewLine ); |
|
if ( nextPosNewLine < 0 ) nextPosNewLine = posNewLine - 1; |
|
line = res.mid( posNewLine, nextPosNewLine - posNewLine ); |
|
|
|
// check when the SB ends: |
|
// * does not starts with prefix or |
|
// * starts with prefix+(any substring of prefix) |
|
if ( ( prefix.isEmpty() && line.indexOf( commonReplySearch ) < 0 ) || |
|
( !prefix.isEmpty() && line.startsWith( prefix ) && |
|
line.mid( prefix.size() ).indexOf( commonReplySearch ) < 0 ) ) |
|
{ |
|
posNewLine = res.indexOf( '\n', posNewLine ) + 1; |
|
} |
|
else |
|
break; // end of the SB |
|
} |
|
// remove the SB or truncate when is the last SB |
|
if ( posNewLine > 0 ) |
|
res.remove( posDeletingStart, posNewLine - posDeletingStart ); |
|
else |
|
res.truncate( posDeletingStart ); |
|
} |
|
return res; |
|
} |
|
|
|
#ifndef KMAIL_UNITTESTS |
|
//----------------------------------------------------------------------------- |
|
QList<int> determineAllowedCtes( const CharFreq& cf, |
|
bool allow8Bit, |
|
bool willBeSigned ) |
|
{ |
|
QList<int> allowedCtes; |
|
|
|
switch ( cf.type() ) { |
|
case CharFreq::SevenBitText: |
|
allowedCtes << DwMime::kCte7bit; |
|
case CharFreq::EightBitText: |
|
if ( allow8Bit ) |
|
allowedCtes << DwMime::kCte8bit; |
|
case CharFreq::SevenBitData: |
|
if ( cf.printableRatio() > 5.0/6.0 ) { |
|
// let n the length of data and p the number of printable chars. |
|
// Then base64 \approx 4n/3; qp \approx p + 3(n-p) |
|
// => qp < base64 iff p > 5n/6. |
|
allowedCtes << DwMime::kCteQp; |
|
allowedCtes << DwMime::kCteBase64; |
|
} else { |
|
allowedCtes << DwMime::kCteBase64; |
|
allowedCtes << DwMime::kCteQp; |
|
} |
|
break; |
|
case CharFreq::EightBitData: |
|
allowedCtes << DwMime::kCteBase64; |
|
break; |
|
case CharFreq::None: |
|
default: |
|
// just nothing (avoid compiler warning) |
|
; |
|
} |
|
|
|
// In the following cases only QP and Base64 are allowed: |
|
// - the buffer will be OpenPGP/MIME signed and it contains trailing |
|
// whitespace (cf. RFC 3156) |
|
// - a line starts with "From " |
|
if ( ( willBeSigned && cf.hasTrailingWhitespace() ) || |
|
cf.hasLeadingFrom() ) { |
|
allowedCtes.removeAll( DwMime::kCte8bit ); |
|
allowedCtes.removeAll( DwMime::kCte7bit ); |
|
} |
|
|
|
return allowedCtes; |
|
} |
|
|
|
AddressList splitAddrField( const QByteArray & str ) |
|
{ |
|
AddressList result; |
|
const char * scursor = str.begin(); |
|
if ( !scursor ) |
|
return AddressList(); |
|
const char * const send = str.begin() + str.length(); |
|
if ( !parseAddressList( scursor, send, result ) ) { |
|
kDebug() << "Error in address splitting: parseAddressList returned false!"; |
|
} |
|
return result; |
|
} |
|
|
|
QString generateMessageId( const QString& addr ) |
|
{ |
|
QDateTime datetime = QDateTime::currentDateTime(); |
|
QString msgIdStr; |
|
|
|
msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" ); |
|
|
|
QString msgIdSuffix; |
|
KConfigGroup general( KMKernel::config(), "General" ); |
|
|
|
if( general.readEntry( "useCustomMessageIdSuffix", false ) ) |
|
msgIdSuffix = general.readEntry( "myMessageIdSuffix" ); |
|
|
|
if( !msgIdSuffix.isEmpty() ) |
|
msgIdStr += '@' + msgIdSuffix; |
|
else |
|
msgIdStr += '.' + KPIMUtils::toIdn( addr ); |
|
|
|
msgIdStr += '>'; |
|
|
|
return msgIdStr; |
|
} |
|
#endif |
|
|
|
QByteArray html2source( const QByteArray & src ) |
|
{ |
|
QByteArray result( 1 + 6*src.length(), '\0' ); // maximal possible length |
|
|
|
QByteArray::ConstIterator s = src.begin(); |
|
QByteArray::Iterator d = result.begin(); |
|
while ( *s ) { |
|
switch ( *s ) { |
|
case '<': { |
|
*d++ = '&'; |
|
*d++ = 'l'; |
|
*d++ = 't'; |
|
*d++ = ';'; |
|
++s; |
|
} |
|
break; |
|
case '\r': { |
|
++s; |
|
} |
|
break; |
|
case '\n': { |
|
*d++ = '<'; |
|
*d++ = 'b'; |
|
*d++ = 'r'; |
|
*d++ = '>'; |
|
++s; |
|
} |
|
break; |
|
case '>': { |
|
*d++ = '&'; |
|
*d++ = 'g'; |
|
*d++ = 't'; |
|
*d++ = ';'; |
|
++s; |
|
} |
|
break; |
|
case '&': { |
|
*d++ = '&'; |
|
*d++ = 'a'; |
|
*d++ = 'm'; |
|
*d++ = 'p'; |
|
*d++ = ';'; |
|
++s; |
|
} |
|
break; |
|
case '"': { |
|
*d++ = '&'; |
|
*d++ = 'q'; |
|
*d++ = 'u'; |
|
*d++ = 'o'; |
|
*d++ = 't'; |
|
*d++ = ';'; |
|
++s; |
|
} |
|
break; |
|
case '\'': { |
|
*d++ = '&'; |
|
*d++ = 'a'; |
|
*d++ = 'p'; |
|
*d++ = 's'; |
|
*d++ = ';'; |
|
++s; |
|
} |
|
break; |
|
default: |
|
*d++ = *s++; |
|
} |
|
} |
|
result.truncate( d - result.begin() ); |
|
return result; |
|
} |
|
|
|
#ifndef KMAIL_UNITTESTS |
|
QString encodeMailtoUrl( const QString& str ) |
|
{ |
|
QString result; |
|
result = QString::fromLatin1( KMMsgBase::encodeRFC2047String( str, |
|
"utf-8" ) ); |
|
result = KUrl::toPercentEncoding( result ); |
|
return result; |
|
} |
|
|
|
QString decodeMailtoUrl( const QString& url ) |
|
{ |
|
QString result; |
|
result = KUrl::fromPercentEncoding( url.toLatin1() ); |
|
result = KMMsgBase::decodeRFC2047String( result.toLatin1() ); |
|
return result; |
|
} |
|
#endif |
|
|
|
QByteArray stripEmailAddr( const QByteArray& aStr ) |
|
{ |
|
//kDebug() << "(" << aStr << ")"; |
|
|
|
if ( aStr.isEmpty() ) |
|
return QByteArray(); |
|
|
|
QByteArray result; |
|
|
|
// The following is a primitive parser for a mailbox-list (cf. RFC 2822). |
|
// The purpose is to extract a displayable string from the mailboxes. |
|
// Comments in the addr-spec are not handled. No error checking is done. |
|
|
|
QByteArray name; |
|
QByteArray comment; |
|
QByteArray angleAddress; |
|
enum { TopLevel, InComment, InAngleAddress } context = TopLevel; |
|
bool inQuotedString = false; |
|
int commentLevel = 0; |
|
|
|
for ( const char* p = aStr.data(); *p; ++p ) { |
|
switch ( context ) { |
|
case TopLevel : { |
|
switch ( *p ) { |
|
case '"' : inQuotedString = !inQuotedString; |
|
break; |
|
case '(' : if ( !inQuotedString ) { |
|
context = InComment; |
|
commentLevel = 1; |
|
} |
|
else |
|
name += *p; |
|
break; |
|
case '<' : if ( !inQuotedString ) { |
|
context = InAngleAddress; |
|
} |
|
else |
|
name += *p; |
|
break; |
|
case '\\' : // quoted character |
|
++p; // skip the '\' |
|
if ( *p ) |
|
name += *p; |
|
break; |
|
case ',' : if ( !inQuotedString ) { |
|
// next email address |
|
if ( !result.isEmpty() ) |
|
result += ", "; |
|
name = name.trimmed(); |
|
comment = comment.trimmed(); |
|
angleAddress = angleAddress.trimmed(); |
|
/* |
|
kDebug() << "Name : \"" << name |
|
<< "\""; |
|
kDebug() << "Comment : \"" << comment |
|
<< "\""; |
|
kDebug() << "Address : \"" << angleAddress |
|
<< "\""; |
|
*/ |
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) { |
|
// handle Outlook-style addresses like |
|
// john.doe@invalid (John Doe) |
|
result += comment; |
|
} |
|
else if ( !name.isEmpty() ) { |
|
result += name; |
|
} |
|
else if ( !comment.isEmpty() ) { |
|
result += comment; |
|
} |
|
else if ( !angleAddress.isEmpty() ) { |
|
result += angleAddress; |
|
} |
|
name = QByteArray(); |
|
comment = QByteArray(); |
|
angleAddress = QByteArray(); |
|
} |
|
else |
|
name += *p; |
|
break; |
|
default : name += *p; |
|
} |
|
break; |
|
} |
|
case InComment : { |
|
switch ( *p ) { |
|
case '(' : ++commentLevel; |
|
comment += *p; |
|
break; |
|
case ')' : --commentLevel; |
|
if ( commentLevel == 0 ) { |
|
context = TopLevel; |
|
comment += ' '; // separate the text of several comments |
|
} |
|
else |
|
comment += *p; |
|
break; |
|
case '\\' : // quoted character |
|
++p; // skip the '\' |
|
if ( *p ) |
|
comment += *p; |
|
break; |
|
default : comment += *p; |
|
} |
|
break; |
|
} |
|
case InAngleAddress : { |
|
switch ( *p ) { |
|
case '"' : inQuotedString = !inQuotedString; |
|
angleAddress += *p; |
|
break; |
|
case '>' : if ( !inQuotedString ) { |
|
context = TopLevel; |
|
} |
|
else |
|
angleAddress += *p; |
|
break; |
|
case '\\' : // quoted character |
|
++p; // skip the '\' |
|
if ( *p ) |
|
angleAddress += *p; |
|
break; |
|
default : angleAddress += *p; |
|
} |
|
break; |
|
} |
|
} // switch ( context ) |
|
} |
|
if ( !result.isEmpty() ) |
|
result += ", "; |
|
name = name.trimmed(); |
|
comment = comment.trimmed(); |
|
angleAddress = angleAddress.trimmed(); |
|
/* |
|
kDebug() << "Name : \"" << name <<"\""; |
|
kDebug() << "Comment : \"" << comment <<"\""; |
|
kDebug() << "Address : \"" << angleAddress <<"\""; |
|
*/ |
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) { |
|
// handle Outlook-style addresses like |
|
// john.doe@invalid (John Doe) |
|
result += comment; |
|
} |
|
else if ( !name.isEmpty() ) { |
|
result += name; |
|
} |
|
else if ( !comment.isEmpty() ) { |
|
result += comment; |
|
} |
|
else if ( !angleAddress.isEmpty() ) { |
|
result += angleAddress; |
|
} |
|
|
|
//kDebug() << "Returns \"" << result << "\""; |
|
return result; |
|
} |
|
|
|
QString stripEmailAddr( const QString& aStr ) |
|
{ |
|
//kDebug() << "(" << aStr << ")"; |
|
|
|
if ( aStr.isEmpty() ) |
|
return QString(); |
|
|
|
QString result; |
|
|
|
// The following is a primitive parser for a mailbox-list (cf. RFC 2822). |
|
// The purpose is to extract a displayable string from the mailboxes. |
|
// Comments in the addr-spec are not handled. No error checking is done. |
|
|
|
QString name; |
|
QString comment; |
|
QString angleAddress; |
|
enum { TopLevel, InComment, InAngleAddress } context = TopLevel; |
|
bool inQuotedString = false; |
|
int commentLevel = 0; |
|
|
|
QChar ch; |
|
int strLength(aStr.length()); |
|
for ( int index = 0; index < strLength; ++index ) { |
|
ch = aStr[index]; |
|
switch ( context ) { |
|
case TopLevel : { |
|
switch ( ch.toLatin1() ) { |
|
case '"' : inQuotedString = !inQuotedString; |
|
break; |
|
case '(' : if ( !inQuotedString ) { |
|
context = InComment; |
|
commentLevel = 1; |
|
} |
|
else |
|
name += ch; |
|
break; |
|
case '<' : if ( !inQuotedString ) { |
|
context = InAngleAddress; |
|
} |
|
else |
|
name += ch; |
|
break; |
|
case '\\' : // quoted character |
|
++index; // skip the '\' |
|
if ( index < aStr.length() ) |
|
name += aStr[index]; |
|
break; |
|
case ',' : if ( !inQuotedString ) { |
|
// next email address |
|
if ( !result.isEmpty() ) |
|
result += ", "; |
|
name = name.trimmed(); |
|
comment = comment.trimmed(); |
|
angleAddress = angleAddress.trimmed(); |
|
/* |
|
kDebug() << "Name : \"" << name |
|
<< "\""; |
|
kDebug() << "Comment : \"" << comment |
|
<< "\""; |
|
kDebug() << "Address : \"" << angleAddress |
|
<< "\""; |
|
*/ |
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) { |
|
// handle Outlook-style addresses like |
|
// john.doe@invalid (John Doe) |
|
result += comment; |
|
} |
|
else if ( !name.isEmpty() ) { |
|
result += name; |
|
} |
|
else if ( !comment.isEmpty() ) { |
|
result += comment; |
|
} |
|
else if ( !angleAddress.isEmpty() ) { |
|
result += angleAddress; |
|
} |
|
name.clear(); |
|
comment.clear(); |
|
angleAddress.clear(); |
|
} |
|
else |
|
name += ch; |
|
break; |
|
default : name += ch; |
|
} |
|
break; |
|
} |
|
case InComment : { |
|
switch ( ch.toLatin1() ) { |
|
case '(' : ++commentLevel; |
|
comment += ch; |
|
break; |
|
case ')' : --commentLevel; |
|
if ( commentLevel == 0 ) { |
|
context = TopLevel; |
|
comment += ' '; // separate the text of several comments |
|
} |
|
else |
|
comment += ch; |
|
break; |
|
case '\\' : // quoted character |
|
++index; // skip the '\' |
|
if ( index < aStr.length() ) |
|
comment += aStr[index]; |
|
break; |
|
default : comment += ch; |
|
} |
|
break; |
|
} |
|
case InAngleAddress : { |
|
switch ( ch.toLatin1() ) { |
|
case '"' : inQuotedString = !inQuotedString; |
|
angleAddress += ch; |
|
break; |
|
case '>' : if ( !inQuotedString ) { |
|
context = TopLevel; |
|
} |
|
else |
|
angleAddress += ch; |
|
break; |
|
case '\\' : // quoted character |
|
++index; // skip the '\' |
|
if ( index < aStr.length() ) |
|
angleAddress += aStr[index]; |
|
break; |
|
default : angleAddress += ch; |
|
} |
|
break; |
|
} |
|
} // switch ( context ) |
|
} |
|
if ( !result.isEmpty() ) |
|
result += ", "; |
|
name = name.trimmed(); |
|
comment = comment.trimmed(); |
|
angleAddress = angleAddress.trimmed(); |
|
/* |
|
kDebug() << "Name : \"" << name <<"\""; |
|
kDebug() << "Comment : \"" << comment <<"\""; |
|
kDebug() << "Address : \"" << angleAddress <<"\""; |
|
*/ |
|
if ( angleAddress.isEmpty() && !comment.isEmpty() ) { |
|
// handle Outlook-style addresses like |
|
// john.doe@invalid (John Doe) |
|
result += comment; |
|
} |
|
else if ( !name.isEmpty() ) { |
|
result += name; |
|
} |
|
else if ( !comment.isEmpty() ) { |
|
result += comment; |
|
} |
|
else if ( !angleAddress.isEmpty() ) { |
|
result += angleAddress; |
|
} |
|
|
|
//kDebug() << "Returns \"" << result << "\""; |
|
return result; |
|
} |
|
|
|
QString quoteHtmlChars( const QString& str, bool removeLineBreaks ) |
|
{ |
|
QString result; |
|
|
|
unsigned int strLength(str.length()); |
|
result.reserve( 6*strLength ); // maximal possible length |
|
for( unsigned int i = 0; i < strLength; ++i ) { |
|
switch ( str[i].toLatin1() ) { |
|
case '<': |
|
result += "<"; |
|
break; |
|
case '>': |
|
result += ">"; |
|
break; |
|
case '&': |
|
result += "&"; |
|
break; |
|
case '"': |
|
result += """; |
|
break; |
|
case '\n': |
|
if ( !removeLineBreaks ) |
|
result += "<br>"; |
|
break; |
|
case '\r': |
|
// ignore CR |
|
break; |
|
default: |
|
result += str[i]; |
|
} |
|
} |
|
|
|
result.squeeze(); |
|
return result; |
|
} |
|
|
|
#ifndef KMAIL_UNITTESTS |
|
QString emailAddrAsAnchor( const QString& aEmail, bool stripped, const QString& cssStyle, |
|
bool aLink ) |
|
{ |
|
if( aEmail.isEmpty() ) |
|
return aEmail; |
|
|
|
const QStringList addressList = KPIMUtils::splitAddressList( aEmail ); |
|
|
|
QString result; |
|
|
|
for( QStringList::ConstIterator it = addressList.constBegin(); |
|
( it != addressList.constEnd() ); |
|
++it ) { |
|
if( !(*it).isEmpty() ) { |
|
QString address = *it; |
|
if( aLink ) { |
|
result += "<a href=\"mailto:" |
|
+ encodeMailtoUrl( address ) |
|
+ "\" "+cssStyle+">"; |
|
} |
|
if( stripped ) |
|
address = stripEmailAddr( address ); |
|
result += quoteHtmlChars( address, true ); |
|
if( aLink ) { |
|
result += "</a>, "; |
|
} |
|
} |
|
} |
|
// cut of the trailing ", " |
|
if( aLink ) { |
|
result.truncate( result.length() - 2 ); |
|
} |
|
|
|
//kDebug() << "('" << aEmail << "') returns:\n-->" << result << "<--"; |
|
return result; |
|
} |
|
|
|
QStringList stripAddressFromAddressList( const QString& address, |
|
const QStringList& list ) |
|
{ |
|
QStringList addresses( list ); |
|
QString addrSpec( KPIMUtils::extractEmailAddress( address ) ); |
|
for ( QStringList::Iterator it = addresses.begin(); |
|
it != addresses.end(); ) { |
|
if ( kasciistricmp( addrSpec.toUtf8().data(), |
|
KPIMUtils::extractEmailAddress( *it ).toUtf8().data() ) == 0 ) { |
|
kDebug() << "Removing" << *it << "from the address list"; |
|
it = addresses.erase( it ); |
|
} |
|
else |
|
++it; |
|
} |
|
return addresses; |
|
} |
|
|
|
QStringList stripMyAddressesFromAddressList( const QStringList& list ) |
|
{ |
|
QStringList addresses = list; |
|
for( QStringList::Iterator it = addresses.begin(); |
|
it != addresses.end(); ) { |
|
kDebug() << "Check whether" << *it <<"is one of my addresses"; |
|
if( kmkernel->identityManager()->thatIsMe( KPIMUtils::extractEmailAddress( *it ) ) ) { |
|
kDebug() << "Removing" << *it <<"from the address list"; |
|
it = addresses.erase( it ); |
|
} |
|
else |
|
++it; |
|
} |
|
return addresses; |
|
} |
|
|
|
bool addressIsInAddressList( const QString& address, |
|
const QStringList& addresses ) |
|
{ |
|
QString addrSpec = KPIMUtils::extractEmailAddress( address ); |
|
for( QStringList::ConstIterator it = addresses.begin(); |
|
it != addresses.end(); ++it ) { |
|
if ( kasciistricmp( addrSpec.toUtf8().data(), |
|
KPIMUtils::extractEmailAddress( *it ).toUtf8().data() ) == 0 ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
QString expandAliases( const QString& recipients, QStringList &distributionListEmpty ) |
|
{ |
|
if ( recipients.isEmpty() ) |
|
return QString(); |
|
|
|
QStringList recipientList = KPIMUtils::splitAddressList( recipients ); |
|
QString expandedRecipients; |
|
for ( QStringList::Iterator it = recipientList.begin(); |
|
it != recipientList.end(); ++it ) { |
|
if ( !expandedRecipients.isEmpty() ) |
|
expandedRecipients += ", "; |
|
QString receiver = (*it).trimmed(); |
|
|
|
// try to expand distribution list |
|
bool distributionListIsEmpty = false; |
|
QString expandedList = KPIM::KAddrBookExternal::expandDistributionList( receiver, distributionListIsEmpty ); |
|
if ( distributionListIsEmpty ) { |
|
expandedRecipients += receiver; |
|
distributionListEmpty << receiver; |
|
continue; |
|
} |
|
|
|
if ( !expandedList.isEmpty()) { |
|
expandedRecipients += expandedList; |
|
continue; |
|
} |
|
|
|
// try to expand nick name |
|
QString expandedNickName = KabcBridge::expandNickName( receiver ); |
|
if ( !expandedNickName.isEmpty() ) { |
|
expandedRecipients += expandedNickName; |
|
continue; |
|
} |
|
|
|
// check whether the address is missing the domain part |
|
QByteArray displayName, addrSpec, comment; |
|
KPIMUtils::splitAddress( receiver.toLatin1(), displayName, addrSpec, comment ); |
|
if ( !addrSpec.contains('@') ) { |
|
KConfigGroup general( KMKernel::config(), "General" ); |
|
QString defaultdomain = general.readEntry( "Default domain" ); |
|
if ( !defaultdomain.isEmpty() ) { |
|
expandedRecipients += KPIMUtils::normalizedAddress( displayName, addrSpec + '@' + defaultdomain, comment ); |
|
} |
|
else { |
|
expandedRecipients += guessEmailAddressFromLoginName( addrSpec ); |
|
} |
|
} |
|
else |
|
expandedRecipients += receiver; |
|
} |
|
|
|
return expandedRecipients; |
|
} |
|
|
|
QString guessEmailAddressFromLoginName( const QString& loginName ) |
|
{ |
|
if ( loginName.isEmpty() ) |
|
return QString(); |
|
|
|
QString address = loginName; |
|
address += '@'; |
|
address += QHostInfo::localHostName(); |
|
|
|
// try to determine the real name |
|
const KUser user( loginName ); |
|
if ( user.isValid() ) { |
|
QString fullName = user.property( KUser::FullName ).toString(); |
|
if ( fullName.contains( QRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) ) |
|
address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" ) |
|
+ "\" <" + address + '>'; |
|
else |
|
address = fullName + " <" + address + '>'; |
|
} |
|
|
|
return address; |
|
} |
|
#endif |
|
|
|
QString smartQuote( const QString & msg, int maxLineLength ) |
|
{ |
|
QStringList part; |
|
QString oldIndent; |
|
bool firstPart = true; |
|
|
|
const QStringList lines = msg.split('\n'); |
|
|
|
QString result; |
|
for(QStringList::const_iterator it = lines.begin(); |
|
it != lines.end(); |
|
++it) |
|
{ |
|
QString line = *it; |
|
|
|
const QString indent = splitLine( line ); |
|
|
|
if ( line.isEmpty()) |
|
{ |
|
if (!firstPart) |
|
part.append(QString()); |
|
continue; |
|
}; |
|
|
|
if (firstPart) |
|
{ |
|
oldIndent = indent; |
|
firstPart = false; |
|
} |
|
|
|
if (oldIndent != indent) |
|
{ |
|
QString fromLine; |
|
// Search if the last non-blank line could be "From" line |
|
if (part.count() && (oldIndent.length() < indent.length())) |
|
{ |
|
QStringList::Iterator it2 = part.isEmpty() ? part.end() : --part.end(); |
|
// FIXME: what if all strings are empty? Then we'll decrement part.begin(). |
|
// Shouldn't we also check for .begin()? |
|
while( (it2 != part.end()) && (*it2).isEmpty()) |
|
--it2; |
|
|
|
if ((it2 != part.end()) && ((*it2).endsWith(':'))) |
|
{ |
|
fromLine = oldIndent + (*it2) + '\n'; |
|
part.erase(it2); |
|
} |
|
} |
|
if (flushPart( result, part, oldIndent, maxLineLength)) |
|
{ |
|
if (oldIndent.length() > indent.length()) |
|
result += indent + '\n'; |
|
else |
|
result += oldIndent + '\n'; |
|
} |
|
if (!fromLine.isEmpty()) |
|
{ |
|
result += fromLine; |
|
} |
|
oldIndent = indent; |
|
} |
|
part.append(line); |
|
} |
|
flushPart( result, part, oldIndent, maxLineLength); |
|
return result; |
|
} |
|
|
|
QString formatString( const QString &wildString, const QString &fromAddr ) |
|
{ |
|
QString result; |
|
|
|
if ( wildString.isEmpty() ) { |
|
return wildString; |
|
} |
|
|
|
unsigned int strLength( wildString.length() ); |
|
for ( uint i=0; i<strLength; ) { |
|
QChar ch = wildString[i++]; |
|
if ( ch == '%' && i<strLength ) { |
|
ch = wildString[i++]; |
|
switch ( ch.toLatin1() ) { |
|
case 'f': // sender's initals |
|
{ |
|
QString str = stripEmailAddr( fromAddr ); |
|
|
|
uint j = 0; |
|
for ( ; str[j]>' '; j++ ) |
|
; |
|
unsigned int strLength( str.length() ); |
|
for ( ; j < strLength && str[j] <= ' '; j++ ) |
|
; |
|
result += str[0]; |
|
if ( str[j] > ' ' ) { |
|
result += str[j]; |
|
} else { |
|
if ( str[1] > ' ' ) { |
|
result += str[1]; |
|
} |
|
} |
|
} |
|
break; |
|
case '_': |
|
result += ' '; |
|
break; |
|
case '%': |
|
result += '%'; |
|
break; |
|
default: |
|
result += '%'; |
|
result += ch; |
|
break; |
|
} |
|
} else { |
|
result += ch; |
|
} |
|
} |
|
return result; |
|
} |
|
|
|
#ifndef KMAIL_UNITTESTS |
|
|
|
void parseMailtoUrl ( const KUrl& url, QString& to, QString& cc, QString& subject, QString& body ) |
|
{ |
|
to = decodeMailtoUrl( url.path() ); |
|
body = url.queryItem( "body" ); |
|
subject = url.queryItem( "subject" ); |
|
kDebug() << url.pathOrUrl(); |
|
cc = url.queryItem( "cc" ); |
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
}
|
|
|