1 /* -*- mode: C++; c-file-style: "gnu" -*-
2 Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
3 Copyright (c) 2010 Leo Franchi <lfranchi@kde.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "messagefactory.h"
22 #include "messageinfo.h"
23 #include "messagecomposersettings.h"
26 #include <akonadi/item.h>
27 #include <messageviewer/kcursorsaver.h>
28 #include <messageviewer/objecttreeparser.h>
29 #include <kpimidentities/identitymanager.h>
30 #include <kpimidentities/identity.h>
32 #include <messagecore/messagestatus.h>
33 #include <kmime/kmime_dateformatter.h>
34 #include <KPIMUtils/Email>
35 #include <messagecore/stringutil.h>
36 #include "messagehelper.h"
37 #include "templateparser/templateparser.h"
38 #include <messagecore/mailinglist-magic.h>
39 #include <KLocalizedString>
40 #include <kcharsets.h>
43 using namespace MessageComposer
;
45 MessageFactory::MessageFactory( const KMime::Message::Ptr
& origMsg
, Akonadi::Item::Id id
, const Akonadi::Collection
& col
)
46 : m_identityManager( 0 )
47 , m_origMsg( KMime::Message::Ptr() )
49 , m_parentFolderId( 0 )
51 , m_replyStrategy( MessageComposer::ReplySmart
)
53 , m_allowDecryption( true )
59 MessageFactory::~MessageFactory()
63 MessageFactory::MessageReply
MessageFactory::createReply()
65 KMime::Message::Ptr
msg( new KMime::Message
);
66 QString str
, mailingListStr
, replyToStr
, toStr
;
67 QByteArray refStr
, headerName
;
70 MessageHelper::initFromMessage( msg
, m_origMsg
, m_identityManager
);
71 MessageCore::MailingList::name( m_origMsg
, headerName
, mailingListStr
);
72 replyToStr
= m_origMsg
->replyTo()->asUnicodeString();
74 msg
->contentType()->setCharset(QString::fromLatin1("utf-8").toLatin1());
76 if ( m_origMsg
->headerByType("List-Post") && m_origMsg
->headerByType("List-Post")->asUnicodeString().contains( QString::fromLatin1("mailto:"), Qt::CaseInsensitive
) ) {
77 QString listPost
= m_origMsg
->headerByType("List-Post")->asUnicodeString();
78 QRegExp
rx( QString::fromLatin1("<mailto:([^@>]+)@([^>]+)>"), Qt::CaseInsensitive
);
79 if ( rx
.indexIn( listPost
, 0 ) != -1 ) // matched
80 m_mailingListAddresses
<< rx
.cap(1) + QChar::fromLatin1('@') + rx
.cap(2);
83 switch( m_replyStrategy
) {
84 case MessageComposer::ReplySmart
: {
85 if ( m_origMsg
->headerByType( "Mail-Followup-To" ) ) {
86 toStr
= m_origMsg
->headerByType( "Mail-Followup-To" )->asUnicodeString();
88 else if ( !replyToStr
.isEmpty() ) {
90 // use the ReplyAll template only when it's a reply to a mailing list
91 if ( m_mailingListAddresses
.isEmpty() )
94 else if ( !m_mailingListAddresses
.isEmpty() ) {
95 toStr
= m_mailingListAddresses
[0];
99 // doesn't seem to be a mailing list, reply to From: address
100 toStr
= m_origMsg
->from()->asUnicodeString();
102 if( m_identityManager
->thatIsMe( KPIMUtils::extractEmailAddress( toStr
) ) ) {
103 // sender seems to be one of our own identities, so we assume that this
104 // is a reply to a "sent" mail where the users wants to add additional
105 // information for the recipient.
106 toStr
= m_origMsg
->to()->asUnicodeString();
111 // strip all my addresses from the list of recipients
112 QStringList recipients
= KPIMUtils::splitAddressList( toStr
);
113 toStr
= MessageCore::StringUtil::stripMyAddressesFromAddressList( recipients
, m_identityManager
).join(QString::fromLatin1(", "));
114 // ... unless the list contains only my addresses (reply to self)
115 if ( toStr
.isEmpty() && !recipients
.isEmpty() )
116 toStr
= recipients
[0];
120 case MessageComposer::ReplyList
: {
121 if ( m_origMsg
->headerByType( "Mail-Followup-To" ) ) {
122 toStr
= m_origMsg
->headerByType( "Mail-Followup-To" )->asUnicodeString();
124 else if ( !m_mailingListAddresses
.isEmpty() ) {
125 toStr
= m_mailingListAddresses
[0];
127 else if ( !replyToStr
.isEmpty() ) {
128 // assume a Reply-To header mangling mailing list
131 // strip all my addresses from the list of recipients
132 QStringList recipients
= KPIMUtils::splitAddressList( toStr
);
133 toStr
= MessageCore::StringUtil::stripMyAddressesFromAddressList( recipients
, m_identityManager
).join(QString::fromLatin1(", "));
137 case MessageComposer::ReplyAll
: {
138 QStringList recipients
;
139 QStringList ccRecipients
;
141 // add addresses from the Reply-To header to the list of recipients
142 if( !replyToStr
.isEmpty() ) {
143 recipients
+= KPIMUtils::splitAddressList( replyToStr
);
144 // strip all possible mailing list addresses from the list of Reply-To
146 for ( QStringList::const_iterator it
= m_mailingListAddresses
.constBegin();
147 it
!= m_mailingListAddresses
.constEnd();
149 recipients
= MessageCore::StringUtil::stripAddressFromAddressList( *it
, recipients
);
153 if ( !m_mailingListAddresses
.isEmpty() ) {
154 // this is a mailing list message
155 if ( recipients
.isEmpty() && !m_origMsg
->from()->asUnicodeString().isEmpty() ) {
156 // The sender didn't set a Reply-to address, so we add the From
157 // address to the list of CC recipients.
158 ccRecipients
+= m_origMsg
->from()->asUnicodeString();
159 kDebug() << "Added" << m_origMsg
->from()->asUnicodeString() <<"to the list of CC recipients";
161 // if it is a mailing list, add the posting address
162 recipients
.prepend( m_mailingListAddresses
[0] );
165 // this is a normal message
166 if ( recipients
.isEmpty() && !m_origMsg
->from()->asUnicodeString().isEmpty() ) {
167 // in case of replying to a normal message only then add the From
168 // address to the list of recipients if there was no Reply-to address
169 recipients
+= m_origMsg
->from()->asUnicodeString();
170 kDebug() << "Added" << m_origMsg
->from()->asUnicodeString() <<"to the list of recipients";
174 // strip all my addresses from the list of recipients
175 toStr
= MessageCore::StringUtil::stripMyAddressesFromAddressList( recipients
, m_identityManager
).join(QString::fromLatin1(", "));
177 // merge To header and CC header into a list of CC recipients
178 if( !m_origMsg
->cc()->asUnicodeString().isEmpty() || !m_origMsg
->to()->asUnicodeString().isEmpty() ) {
180 if (!m_origMsg
->to()->asUnicodeString().isEmpty())
181 list
+= KPIMUtils::splitAddressList(m_origMsg
->to()->asUnicodeString());
182 if (!m_origMsg
->cc()->asUnicodeString().isEmpty())
183 list
+= KPIMUtils::splitAddressList(m_origMsg
->cc()->asUnicodeString());
184 for( QStringList::ConstIterator it
= list
.constBegin(); it
!= list
.constEnd(); ++it
) {
185 if( !MessageCore::StringUtil::addressIsInAddressList( *it
, recipients
)
186 && !MessageCore::StringUtil::addressIsInAddressList( *it
, ccRecipients
) ) {
188 kDebug() << "Added" << *it
<<"to the list of CC recipients";
193 if ( !ccRecipients
.isEmpty() ) {
194 // strip all my addresses from the list of CC recipients
195 ccRecipients
= MessageCore::StringUtil::stripMyAddressesFromAddressList( ccRecipients
, m_identityManager
);
197 // in case of a reply to self toStr might be empty. if that's the case
198 // then propagate a cc recipient to To: (if there is any).
199 if ( toStr
.isEmpty() && !ccRecipients
.isEmpty() ) {
200 toStr
= ccRecipients
[0];
201 ccRecipients
.pop_front();
204 msg
->cc()->fromUnicodeString( ccRecipients
.join(QString::fromLatin1(", ")), "utf-8" );
207 if ( toStr
.isEmpty() && !recipients
.isEmpty() ) {
208 // reply to self without other recipients
209 toStr
= recipients
[0];
213 case MessageComposer::ReplyAuthor
: {
214 if ( !replyToStr
.isEmpty() ) {
215 QStringList recipients
= KPIMUtils::splitAddressList( replyToStr
);
216 // strip the mailing list post address from the list of Reply-To
217 // addresses since we want to reply in private
218 for ( QStringList::const_iterator it
= m_mailingListAddresses
.constBegin();
219 it
!= m_mailingListAddresses
.constEnd();
221 recipients
= MessageCore::StringUtil::stripAddressFromAddressList( *it
, recipients
);
223 if ( !recipients
.isEmpty() ) {
224 toStr
= recipients
.join(QString::fromLatin1(", "));
227 // there was only the mailing list post address in the Reply-To header,
228 // so use the From address instead
229 toStr
= m_origMsg
->from()->asUnicodeString();
232 else if ( ! m_origMsg
->from()->asUnicodeString().isEmpty() ) {
233 toStr
= m_origMsg
->from()->asUnicodeString();
238 case MessageComposer::ReplyNone
: {
239 // the addressees will be set by the caller
243 msg
->to()->fromUnicodeString(toStr
, "utf-8");
245 refStr
= getRefStr( m_origMsg
);
246 if (!refStr
.isEmpty())
247 msg
->references()->fromUnicodeString (QString::fromLocal8Bit(refStr
), QString::fromLatin1("utf-8").toLatin1() );
248 //In-Reply-To = original msg-id
249 msg
->inReplyTo()->fromUnicodeString( m_origMsg
->messageID()->asUnicodeString(), "utf-8" );
251 msg
->subject()->fromUnicodeString( MessageHelper::replySubject( m_origMsg
), "utf-8" );
253 // If the reply shouldn't be blank, apply the template to the message
255 TemplateParser::TemplateParser
parser( msg
, (replyAll
? TemplateParser::TemplateParser::ReplyAll
: TemplateParser::TemplateParser::Reply
) );
256 parser
.setIdentityManager( m_identityManager
);
257 parser
.setCharsets( MessageComposerSettings::self()->preferredCharsets() );
258 if ( MessageComposer::MessageComposerSettings::quoteSelectionOnly() ) {
259 parser
.setSelection( m_selection
);
261 parser
.setAllowDecryption( m_allowDecryption
);
262 if ( !m_template
.isEmpty() )
263 parser
.process( m_template
, m_origMsg
);
265 parser
.process( m_origMsg
, m_collection
);
270 link( msg
, m_id
, KPIM::MessageStatus::statusReplied() );
271 if ( m_parentFolderId
> 0 ) {
272 KMime::Headers::Generic
*header
= new KMime::Headers::Generic( "X-KMail-Fcc", msg
.get(), QString::number( m_parentFolderId
), "utf-8" );
273 msg
->setHeader( header
);
276 if( m_origMsg
->hasHeader( QLatin1String("X-KMail-EncryptActionEnabled").latin1() ) &&
277 m_origMsg
->headerByType( QLatin1String("X-KMail-EncryptActionEnabled").latin1() )->as7BitString() == "true" ) {
278 msg
->setHeader( new KMime::Headers::Generic( "X-KMail-EncryptActionEnabled", msg
.get(), QLatin1String("true"), "utf-8" ) );
284 reply
.replyAll
= replyAll
;
290 KMime::Message::Ptr
MessageFactory::createForward()
292 KMime::Message::Ptr
msg( new KMime::Message
);
294 // This is a non-multipart, non-text mail (e.g. text/calendar). Construct
295 // a multipart/mixed mail and add the original body as an attachment.
296 if ( !m_origMsg
->contentType()->isMultipart() &&
297 ( !m_origMsg
->contentType()->isText() ||
298 ( m_origMsg
->contentType()->isText() && m_origMsg
->contentType()->subType() != "html"
299 && m_origMsg
->contentType()->subType() != "plain" ) ) ) {
300 MessageHelper::initFromMessage( msg
, m_origMsg
, m_identityManager
);
301 msg
->removeHeader("Content-Type");
302 msg
->removeHeader("Content-Transfer-Encoding");
304 msg
->contentType()->setMimeType( "multipart/mixed" );
306 //TODO: Andras: somebody should check if this is correct. :)
308 KMime::Content
*msgPart
= new KMime::Content
;
309 msgPart
->contentType()->setMimeType("text/plain");
310 msg
->addContent( msgPart
);
312 // the old contents of the mail
313 KMime::Content
*secondPart
= new KMime::Content
;
314 secondPart
->contentType()->setMimeType( m_origMsg
->contentType()->mimeType() );
315 secondPart
->setBody( m_origMsg
->body() );
316 // use the headers of the original mail
317 secondPart
->setHead( m_origMsg
->head() );
318 msg
->addContent( secondPart
);
322 // Normal message (multipart or text/plain|html)
323 // Just copy the message, the template parser will do the hard work of
324 // replacing the body text in TemplateParser::addProcessedBodyToMessage()
326 //TODO Check if this is ok
327 msg
->setHead( m_origMsg
->head() );
328 msg
->setBody( m_origMsg
->body() );
329 QString oldContentType
= msg
->contentType()->asUnicodeString();
330 MessageHelper::initFromMessage( msg
, m_origMsg
, m_identityManager
);
332 // restore the content type, MessageHelper::initFromMessage() sets the contents type to
333 // text/plain, via initHeader(), for unclear reasons
334 msg
->contentType()->fromUnicodeString( oldContentType
, "utf-8" );
338 msg
->subject()->fromUnicodeString( MessageHelper::forwardSubject( m_origMsg
), "utf-8" );
340 TemplateParser::TemplateParser
parser( msg
, TemplateParser::TemplateParser::Forward
);
341 parser
.setIdentityManager( m_identityManager
);
342 parser
.setCharsets( MessageComposerSettings::self()->preferredCharsets() );
343 if ( !m_template
.isEmpty() )
344 parser
.process( m_template
, m_origMsg
);
346 parser
.process( m_origMsg
, m_collection
);
350 link( msg
, m_id
, KPIM::MessageStatus::statusForwarded() );
355 QPair
< KMime::Message::Ptr
, QList
< KMime::Content
* > > MessageFactory::createAttachedForward(QList
< KMime::Message::Ptr
> msgs
)
357 // create forwarded message with original message as attachment
358 // remove headers that shouldn't be forwarded
359 KMime::Message::Ptr
msg( new KMime::Message
);
360 QList
< KMime::Content
* > attachments
;
362 if( msgs
.count() == 0 )
365 if( msgs
.count() >= 2 ) {
366 // don't respect X-KMail-Identity headers because they might differ for
367 // the selected mails
368 MessageHelper::initHeader( msg
, m_identityManager
, m_origId
);
369 } else if( msgs
.count() == 1 ) {
370 MessageHelper::initFromMessage( msg
, msgs
.first(), m_identityManager
);
371 msg
->subject()->fromUnicodeString( MessageHelper::forwardSubject( msgs
.first() ),"utf-8" );
374 MessageHelper::setAutomaticFields( msg
, true );
375 MessageViewer::KCursorSaver
busy( MessageViewer::KBusyPtr::busy() );
376 // iterate through all the messages to be forwarded
377 foreach ( const KMime::Message::Ptr
& fwdMsg
, msgs
) {
378 // remove headers that shouldn't be forwarded
379 MessageCore::StringUtil::removePrivateHeaderFields( fwdMsg
);
380 fwdMsg
->removeHeader("BCC");
382 KMime::Content
*msgPart
= new KMime::Content( fwdMsg
.get() );
383 msgPart
->contentType()->setMimeType( "message/rfc822" );
385 msgPart
->contentDisposition()->setParameter( QLatin1String( "name" ), i18n( "forwarded message" ) );
386 msgPart
->contentDisposition()->setDisposition( KMime::Headers::CDinline
);
387 msgPart
->contentDescription()->fromUnicodeString( fwdMsg
->from()->asUnicodeString() + QLatin1String( ": " ) + fwdMsg
->subject()->asUnicodeString(), "utf-8" );
388 msgPart
->setBody( fwdMsg
->encodedContent() );
392 // THIS HAS TO BE AFTER setCte()!!!!
393 msgPart
->setCharset( "" );
395 kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO
;
397 link( fwdMsg
, m_origId
, KPIM::MessageStatus::statusForwarded() );
398 attachments
<< msgPart
;
403 link( msg
, m_id
, KPIM::MessageStatus::statusForwarded() );
405 return QPair
< KMime::Message::Ptr
, QList
< KMime::Content
* > >( msg
, QList
< KMime::Content
* >() << attachments
);
409 KMime::Message::Ptr
MessageFactory::createResend()
411 KMime::Message::Ptr
msg( new KMime::Message
);
412 msg
->setContent( m_origMsg
->encodedContent() );
414 msg
->removeHeader( "Message-Id" );
415 uint originalIdentity
= identityUoid( m_origMsg
);
417 // Set the identity from above
418 KMime::Headers::Generic
*header
= new KMime::Headers::Generic( "X-KMail-Identity", msg
.get(), QString::number( originalIdentity
), "utf-8" );
419 msg
->setHeader( header
);
421 // Restore the original bcc field as this is overwritten in applyIdentity
422 msg
->bcc( m_origMsg
->bcc() );
426 KMime::Message::Ptr
MessageFactory::createRedirect( const QString
&toStr
)
429 return KMime::Message::Ptr();
431 // copy the message 1:1
432 KMime::Message::Ptr
msg( new KMime::Message
);
433 msg
->setContent( m_origMsg
->encodedContent() );
437 QString strId
= msg
->headerByType( "X-KMail-Identity" ) ? msg
->headerByType( "X-KMail-Identity" )->asUnicodeString().trimmed() : QString::fromLocal8Bit("");
438 if ( !strId
.isEmpty())
440 const KPIMIdentities::Identity
& ident
=
441 m_identityManager
->identityForUoidOrDefault( id
);
443 // X-KMail-Redirect-From: content
444 QString strByWayOf
= QString::fromLocal8Bit("%1 (by way of %2 <%3>)")
445 .arg( m_origMsg
->from()->asUnicodeString() )
446 .arg( ident
.fullName() )
447 .arg( ident
.emailAddr() );
449 // Resent-From: content
450 QString strFrom
= QString::fromLocal8Bit("%1 <%2>")
451 .arg( ident
.fullName() )
452 .arg( ident
.emailAddr() );
454 // format the current date to be used in Resent-Date:
455 QString newDate
= KDateTime::currentLocalDateTime().toString( KDateTime::RFCDateDay
);
457 // prepend Resent-*: headers (c.f. RFC2822 3.6.6)
459 if ( MessageComposer::MessageComposerSettings::useCustomMessageIdSuffix() ) {
460 msgIdSuffix
= MessageComposer::MessageComposerSettings::customMsgIDSuffix();
462 KMime::Headers::Generic
*header
=
463 new KMime::Headers::Generic( "Resent-Message-ID", msg
.get(),
464 MessageCore::StringUtil::generateMessageId(
465 msg
->sender()->asUnicodeString(), msgIdSuffix
), "utf-8" );
466 msg
->setHeader( header
);
468 header
= new KMime::Headers::Generic( "Resent-Date", msg
.get(), newDate
, "utf-8" );
469 msg
->setHeader( header
);
471 header
= new KMime::Headers::Generic( "Resent-From", msg
.get(), strFrom
, "utf-8" );
472 msg
->setHeader( header
);
474 KMime::Headers::To
* headerT
= new KMime::Headers::To( msg
.get(), toStr
, "utf-8" );
475 msg
->setHeader( headerT
);
477 header
= new KMime::Headers::Generic( "Resent-To", msg
.get(), toStr
, "utf-8" );
478 msg
->setHeader( header
);
480 if( msg
->cc( false ) ) {
481 header
= new KMime::Headers::Generic( "Resent-Cc", msg
.get(), m_origMsg
->cc()->asUnicodeString(), "utf-8" );
482 msg
->setHeader( header
);
485 if( msg
->bcc( false ) ) {
486 header
= new KMime::Headers::Generic( "Resent-Bcc", msg
.get(), m_origMsg
->bcc()->asUnicodeString(), "utf-8" );
487 msg
->setHeader( header
);
490 header
= new KMime::Headers::Generic( "X-KMail-Redirect-From", msg
.get(), strByWayOf
, "utf-8" );
491 msg
->setHeader( header
);
492 header
= new KMime::Headers::Generic( "X-KMail-Recipients", msg
.get(), toStr
, "utf-8" );
493 msg
->setHeader( header
);
496 link( msg
, m_id
, KPIM::MessageStatus::statusForwarded() );
501 KMime::Message::Ptr
MessageFactory::createDeliveryReceipt()
503 QString str
, receiptTo
;
504 KMime::Message::Ptr receipt
;
506 receiptTo
= m_origMsg
->headerByType("Disposition-Notification-To") ? m_origMsg
->headerByType("Disposition-Notification-To")->asUnicodeString() : QString::fromLatin1("");
507 if ( receiptTo
.trimmed().isEmpty() )
508 return KMime::Message::Ptr();
509 receiptTo
.remove( QChar::fromLatin1('\n') );
511 receipt
= KMime::Message::Ptr( new KMime::Message
);
512 MessageHelper::initFromMessage( receipt
, m_origMsg
, m_identityManager
);
513 receipt
->to()->fromUnicodeString( receiptTo
, QString::fromLatin1("utf-8").toLatin1() );
514 receipt
->subject()->fromUnicodeString( i18n("Receipt: ") + m_origMsg
->subject()->asUnicodeString(), "utf-8");
516 str
= QString::fromLatin1("Your message was successfully delivered.");
517 str
+= QString::fromLatin1("\n\n---------- Message header follows ----------\n");
518 str
+= QString::fromLatin1(m_origMsg
->head());
519 str
+= QString::fromLatin1("--------------------------------------------\n");
520 // Conversion to toLatin1 is correct here as Mail headers should contain
522 receipt
->setBody(str
.toLatin1());
523 MessageHelper::setAutomaticFields( receipt
);
529 KMime::Message::Ptr
MessageFactory::createMDN( KMime::MDN::ActionMode a
,
530 KMime::MDN::DispositionType d
,
531 KMime::MDN::SendingMode s
,
532 int mdnQuoteOriginal
,
533 QList
<KMime::MDN::DispositionModifier
> m
)
535 // RFC 2298: At most one MDN may be issued on behalf of each
536 // particular recipient by their user agent. That is, once an MDN
537 // has been issued on behalf of a recipient, no further MDNs may be
538 // issued on behalf of that recipient, even if another disposition
539 // is performed on the message.
540 //#define MDN_DEBUG 1
542 if ( MessageInfo::instance()->mdnSentState( m_origMsg
.get() ) != KMMsgMDNStateUnknown
&&
543 MessageInfo::instance()->mdnSentState( m_origMsg
.get() ) != KMMsgMDNNone
)
544 return KMime::Message::Ptr();
546 char st
[2]; st
[0] = (char)MessageInfo::instance()->mdnSentState( m_origMsg
); st
[1] = 0;
547 kDebug() << "mdnSentState() == '" << st
<<"'";
550 // RFC 2298: An MDN MUST NOT be generated in response to an MDN.
551 if ( MessageViewer::ObjectTreeParser::findType( m_origMsg
.get(), "message",
552 "disposition-notification", true, true ) ) {
553 MessageInfo::instance()->setMDNSentState( m_origMsg
.get(), KMMsgMDNIgnore
);
554 return KMime::Message::Ptr();
557 // extract where to send to:
558 QString receiptTo
= m_origMsg
->headerByType("Disposition-Notification-To") ? m_origMsg
->headerByType("Disposition-Notification-To")->asUnicodeString() : QString::fromLatin1("");
559 if( receiptTo
.trimmed().isEmpty() ) return KMime::Message::Ptr( new KMime::Message
);
560 receiptTo
.remove( QChar::fromLatin1('\n') );
563 QString special
; // fill in case of error, warning or failure
565 // extract where to send from:
566 QString finalRecipient
= m_identityManager
->identityForUoidOrDefault( identityUoid( m_origMsg
) ).fullEmailAddr();
572 KMime::Message::Ptr
receipt( new KMime::Message() );
573 MessageHelper::initFromMessage( receipt
, m_origMsg
, m_identityManager
);
574 receipt
->contentType()->from7BitString( "multipart/report" );
575 receipt
->contentType()->setBoundary( KMime::multiPartBoundary() );
576 receipt
->contentType()->setCharset( "us-ascii" );
577 receipt
->removeHeader("Content-Transfer-Encoding");
578 // Modify the ContentType directly (replaces setAutomaticFields(true))
579 receipt
->contentType()->setParameter( QString::fromLatin1("report-type"), QString::fromLatin1("disposition-notification") );
582 QString description
= replaceHeadersInString( m_origMsg
, KMime::MDN::descriptionFor( d
, m
) );
585 KMime::Content
* firstMsgPart
= new KMime::Content( m_origMsg
.get() );
586 firstMsgPart
->contentType()->setMimeType( "text/plain" );
587 firstMsgPart
->contentType()->setCharset( "us-ascii" );
588 firstMsgPart
->contentTransferEncoding()->from7BitString( "7bit" );
589 firstMsgPart
->setBody( description
.toUtf8() );
590 receipt
->addContent( firstMsgPart
);
592 // message/disposition-notification part:
593 KMime::Content
* secondMsgPart
= new KMime::Content( m_origMsg
.get() );
594 secondMsgPart
->contentType()->setMimeType( "message/disposition-notification" );
595 //secondMsgPart.setCharset( "us-ascii" );
596 secondMsgPart
->contentTransferEncoding()->from7BitString( "7bit" );
597 secondMsgPart
->setBody( KMime::MDN::dispositionNotificationBodyContent(
599 m_origMsg
->headerByType("Original-Recipient") ? m_origMsg
->headerByType("Original-Recipient")->as7BitString() : "",
600 m_origMsg
->messageID()->as7BitString(), /* Message-ID */
601 d
, a
, s
, m
, special
) );
602 receipt
->addContent( secondMsgPart
);
604 if ( mdnQuoteOriginal
< 0 || mdnQuoteOriginal
> 2 ) mdnQuoteOriginal
= 0;
605 /* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/
607 KMime::Content
* thirdMsgPart
= new KMime::Content( m_origMsg
.get() );
608 switch ( mdnQuoteOriginal
) {
610 thirdMsgPart
->contentType()->setMimeType( "message/rfc822" );
611 thirdMsgPart
->setBody( MessageCore::StringUtil::asSendableString( m_origMsg
) );
612 receipt
->addContent( thirdMsgPart
);
615 thirdMsgPart
->contentType()->setMimeType( "text/rfc822-headers" );
616 thirdMsgPart
->setBody( MessageCore::StringUtil::headerAsSendableString( m_origMsg
) );
617 receipt
->addContent( thirdMsgPart
);
624 receipt
->to()->fromUnicodeString( receiptTo
, "utf-8" );
625 receipt
->subject()->from7BitString( "Message Disposition Notification" );
626 KMime::Headers::InReplyTo
*header
= new KMime::Headers::InReplyTo( receipt
.get(), m_origMsg
->messageID()->asUnicodeString(), "utf-8" );
627 receipt
->setHeader( header
);
629 receipt
->references()->from7BitString( getRefStr( m_origMsg
) );
634 kDebug() << "final message:" + receipt
->encodedContent();
637 // Set "MDN sent" status:
639 KMMsgMDNSentState state
= KMMsgMDNStateUnknown
;
641 case KMime::MDN::Displayed
: state
= KMMsgMDNDisplayed
; break;
642 case KMime::MDN::Deleted
: state
= KMMsgMDNDeleted
; break;
643 case KMime::MDN::Dispatched
: state
= KMMsgMDNDispatched
; break;
644 case KMime::MDN::Processed
: state
= KMMsgMDNProcessed
; break;
645 case KMime::MDN::Denied
: state
= KMMsgMDNDenied
; break;
646 case KMime::MDN::Failed
: state
= KMMsgMDNFailed
; break;
648 MessageInfo::instance()->setMDNSentState( m_origMsg
.get(), state
);
654 QPair
< KMime::Message::Ptr
, KMime::Content
* > MessageFactory::createForwardDigestMIME( QList
< KMime::Message::Ptr
> msgs
)
656 KMime::Message::Ptr
msg( new KMime::Message
);
657 KMime::Content
* digest
= new KMime::Content( msg
.get() );
659 QString mainPartText
= i18n("\nThis is a MIME digest forward. The content of the"
660 " message is contained in the attachment(s).\n\n\n");
662 digest
->contentType()->setMimeType( "multipart/digest" );
663 digest
->contentType()->setBoundary( KMime::multiPartBoundary() );
664 digest
->contentDescription()->fromUnicodeString( QString::fromLatin1("Digest of %1 messages.").arg( msgs
.size() ), "utf8" );
665 digest
->contentDisposition()->setFilename( QLatin1String( "digest" ) );
666 digest
->fromUnicodeString( mainPartText
);
670 foreach( const KMime::Message::Ptr fMsg
, msgs
) {
671 if( id
== 0 && fMsg
->hasHeader( "X-KMail-Identity" ) )
672 id
= fMsg
->headerByType( "X-KMail-Identity" )->asUnicodeString().toInt();
674 MessageCore::StringUtil::removePrivateHeaderFields( fMsg
);
675 fMsg
->bcc()->clear();
677 KMime::Content
* part
= new KMime::Content( digest
);
679 part
->contentType()->setMimeType( "message/rfc822" );
680 part
->contentType()->setCharset( fMsg
->contentType()->charset() );
681 part
->contentID()->setIdentifier( fMsg
->contentID()->identifier() );
682 part
->contentDescription()->fromUnicodeString( fMsg
->contentDescription()->asUnicodeString(), "utf8" );
683 part
->contentDisposition()->setParameter( QLatin1String( "name" ), i18n( "forwarded message" ) );
684 part
->fromUnicodeString( QString::fromLatin1( fMsg
->encodedContent() ) );
687 link( fMsg
, m_origId
, KPIM::MessageStatus::statusForwarded() );
688 digest
->addContent( part
);
693 MessageHelper::initHeader( msg
, m_identityManager
, id
);
695 // kDebug() << "digest:" << digest->contents().size() << digest->encodedContent();
697 return QPair
< KMime::Message::Ptr
, KMime::Content
* >( msg
, digest
);
701 void MessageFactory::setIdentityManager( KPIMIdentities::IdentityManager
* ident
)
703 m_identityManager
= ident
;
706 void MessageFactory::setMessageItemID( Akonadi::Entity::Id id
)
711 void MessageFactory::setReplyStrategy( MessageComposer::ReplyStrategy replyStrategy
)
713 m_replyStrategy
= replyStrategy
;
716 void MessageFactory::setSelection( const QString
& selection
)
718 m_selection
= selection
;
721 void MessageFactory::setQuote( bool quote
)
726 void MessageFactory::setAllowDecryption( bool allowD
)
728 m_allowDecryption
= allowD
;
731 void MessageFactory::setTemplate( const QString
& templ
)
736 void MessageFactory::setMailingListAddresses( const QStringList
& listAddresses
)
738 m_mailingListAddresses
<< listAddresses
;
741 void MessageFactory::setFolderIdentity( Akonadi::Entity::Id folderIdentityId
)
743 m_folderId
= folderIdentityId
;
746 void MessageFactory::putRepliesInSameFolder( Akonadi::Entity::Id parentColId
)
748 m_parentFolderId
= parentColId
;
751 bool MessageFactory::MDNRequested(KMime::Message::Ptr msg
)
753 // extract where to send to:
754 QString receiptTo
= msg
->headerByType("Disposition-Notification-To") ? msg
->headerByType("Disposition-Notification-To")->asUnicodeString() : QString::fromLatin1("");
755 if ( receiptTo
.trimmed().isEmpty() ) return false;
756 receiptTo
.remove( QChar::fromLatin1('\n') );
757 return !receiptTo
.isEmpty();
761 bool MessageFactory::MDNConfirmMultipleRecipients( KMime::Message::Ptr msg
)
763 // extract where to send to:
764 QString receiptTo
= msg
->headerByType("Disposition-Notification-To") ? msg
->headerByType("Disposition-Notification-To")->asUnicodeString() : QString::fromLatin1("");
765 if ( receiptTo
.trimmed().isEmpty() ) return false;
766 receiptTo
.remove( QChar::fromLatin1('\n') );
768 // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
769 // MDN sent) ] if there is more than one distinct address in the
770 // Disposition-Notification-To header.
771 kDebug() << "KPIMUtils::splitAddressList(receiptTo):" // krazy:exclude=kdebug
772 << KPIMUtils::splitAddressList(receiptTo
).join(QString::fromLatin1("\n"));
774 return KPIMUtils::splitAddressList(receiptTo
).count() > 1;
777 bool MessageFactory::MDNReturnPathEmpty( KMime::Message::Ptr msg
)
779 // extract where to send to:
780 QString receiptTo
= msg
->headerByType("Disposition-Notification-To") ? msg
->headerByType("Disposition-Notification-To")->asUnicodeString() : QString::fromLatin1("");
781 if ( receiptTo
.trimmed().isEmpty() ) return false;
782 receiptTo
.remove( QChar::fromLatin1('\n') );
784 // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
785 // the Disposition-Notification-To header differs from the address
786 // in the Return-Path header. [...] Confirmation from the user
787 // SHOULD be obtained (or no MDN sent) if there is no Return-Path
788 // header in the message [...]
789 KMime::Types::AddrSpecList returnPathList
= MessageHelper::extractAddrSpecs( msg
, "Return-Path");
790 QString returnPath
= returnPathList
.isEmpty() ? QString()
791 : returnPathList
.front().localPart
+ QChar::fromLatin1('@') + returnPathList
.front().domain
;
792 kDebug() << "clean return path:" << returnPath
;
793 return returnPath
.isEmpty();
796 bool MessageFactory::MDNReturnPathNotInRecieptTo( KMime::Message::Ptr msg
)
798 // extract where to send to:
799 QString receiptTo
= msg
->headerByType("Disposition-Notification-To") ? msg
->headerByType("Disposition-Notification-To")->asUnicodeString() : QString::fromLatin1("");
800 if ( receiptTo
.trimmed().isEmpty() ) return false;
801 receiptTo
.remove( QChar::fromLatin1('\n') );
803 // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
804 // the Disposition-Notification-To header differs from the address
805 // in the Return-Path header. [...] Confirmation from the user
806 // SHOULD be obtained (or no MDN sent) if there is no Return-Path
807 // header in the message [...]
808 KMime::Types::AddrSpecList returnPathList
= MessageHelper::extractAddrSpecs( msg
, QString::fromLatin1("Return-Path").toLatin1());
809 QString returnPath
= returnPathList
.isEmpty() ? QString()
810 : returnPathList
.front().localPart
+ QChar::fromLatin1('@') + returnPathList
.front().domain
;
811 kDebug() << "clean return path:" << returnPath
;
812 return !receiptTo
.contains( returnPath
, Qt::CaseSensitive
);
815 bool MessageFactory::MDNMDNUnknownOption( KMime::Message::Ptr msg
)
818 // RFC 2298: An importance of "required" indicates that
819 // interpretation of the parameter is necessary for proper
820 // generation of an MDN in response to this request. If a UA does
821 // not understand the meaning of the parameter, it MUST NOT generate
822 // an MDN with any disposition type other than "failed" in response
824 QString notificationOptions
= msg
->headerByType("Disposition-Notification-Options") ? msg
->headerByType("Disposition-Notification-Options")->asUnicodeString() : QString::fromLatin1("");
825 if ( notificationOptions
.contains( QString::fromLatin1("required"), Qt::CaseSensitive
) ) {
826 // ### hacky; should parse...
827 // There is a required option that we don't understand. We need to
828 // ask the user what we should do:
834 void MessageFactory::link( const KMime::Message::Ptr
&msg
, Akonadi::Item::Id id
, const KPIM::MessageStatus
& aStatus
)
836 Q_ASSERT( aStatus
.isReplied() || aStatus
.isForwarded() || aStatus
.isDeleted() );
838 QString message
= msg
->headerByType( "X-KMail-Link-Message" ) ? msg
->headerByType( "X-KMail-Link-Message" )->asUnicodeString() : QString();
839 if ( !message
.isEmpty() )
840 message
+= QChar::fromLatin1(',');
841 QString type
= msg
->headerByType( "X-KMail-Link-Type" ) ? msg
->headerByType( "X-KMail-Link-Type" )->asUnicodeString(): QString();
842 if ( !type
.isEmpty() )
843 type
+= QChar::fromLatin1(',');
845 message
+= QString::number( id
);
846 if ( aStatus
.isReplied() )
847 type
+= QString::fromLatin1("reply");
848 else if ( aStatus
.isForwarded() )
849 type
+= QString::fromLatin1("forward");
850 else if ( aStatus
.isDeleted() )
851 type
+= QString::fromLatin1("deleted");
853 KMime::Headers::Generic
*header
= new KMime::Headers::Generic( "X-KMail-Link-Message", msg
.get(), message
, QString::fromLatin1("utf-8").toLatin1() );
854 msg
->setHeader( header
);
856 header
= new KMime::Headers::Generic( "X-KMail-Link-Type", msg
.get(), type
, QString::fromLatin1("utf-8").toLatin1() );
857 msg
->setHeader( header
);
861 uint
MessageFactory::identityUoid( const KMime::Message::Ptr
&msg
)
864 if ( msg
->headerByType("X-KMail-Identity") )
865 idString
= msg
->headerByType("X-KMail-Identity")->asUnicodeString().trimmed();
867 int id
= idString
.toUInt( &ok
);
869 if ( !ok
|| id
== 0 )
870 id
= m_identityManager
->identityForAddress( msg
->to()->asUnicodeString() + QString::fromLatin1(", ") + msg
->cc()->asUnicodeString() ).uoid();
872 if ( id
== 0 && m_folderId
> 0 ) {
879 QString
MessageFactory::replaceHeadersInString( const KMime::Message::Ptr
&msg
, const QString
& s
)
882 QRegExp
rx( QString::fromLatin1("\\$\\{([a-z0-9-]+)\\}"), Qt::CaseInsensitive
);
883 Q_ASSERT( rx
.isValid() );
885 QRegExp
rxDate( QString::fromLatin1("\\$\\{date\\}") );
886 Q_ASSERT( rxDate
.isValid() );
888 kDebug() << "creating mdn date:" << msg
->date()->dateTime().dateTime().toTime_t() << KMime::DateFormatter::formatDate(
889 KMime::DateFormatter::Localized
, msg
->date()->dateTime().dateTime().toTime_t() );
890 QString sDate
= KMime::DateFormatter::formatDate(
891 KMime::DateFormatter::Localized
, msg
->date()->dateTime().dateTime().toTime_t() );
894 if( ( idx
= rxDate
.indexIn( result
, idx
) ) != -1 ) {
895 result
.replace( idx
, rxDate
.matchedLength(), sDate
);
899 while ( ( idx
= rx
.indexIn( result
, idx
) ) != -1 ) {
900 QString replacement
= msg
->headerByType( rx
.cap(1).toLatin1() ) ? msg
->headerByType( rx
.cap(1).toLatin1() )->asUnicodeString() : QString::fromLatin1("");
901 result
.replace( idx
, rx
.matchedLength(), replacement
);
902 idx
+= replacement
.length();
907 void MessageFactory::applyCharset( const KMime::Message::Ptr msg
)
909 if( MessageComposer::MessageComposerSettings::forceReplyCharset() ) {
910 msg
->contentType()->setCharset( m_origMsg
->contentType()->charset() );
912 QTextCodec
*codec
= KGlobal::charsets()->codecForName( QString::fromLatin1( msg
->contentType()->charset() ) );
914 kError() << "Could not get text codec for charset" << msg
->contentType()->charset();
915 } else if( !codec
->canEncode( QString::fromUtf8( msg
->body() ) ) ) { // charset can't encode body, fall back to preferred
916 const QStringList charsets
= MessageComposer::MessageComposerSettings::preferredCharsets();
917 QList
<QByteArray
> chars
;
918 foreach( QString charset
, charsets
)
919 chars
<< charset
.toAscii();
920 QByteArray fallbackCharset
= Message::Util::selectCharset( chars
, QString::fromUtf8( msg
->body() ) );
921 if( fallbackCharset
.isEmpty() ) // UTF-8 as fall-through
922 fallbackCharset
= "UTF-8";
923 codec
= KGlobal::charsets()->codecForName( QString::fromLatin1( fallbackCharset
) );
924 msg
->setBody( codec
->fromUnicode( QString::fromUtf8( msg
->body() ) ) );
926 msg
->setBody( codec
->fromUnicode( QString::fromUtf8( msg
->body() ) ) );
932 QByteArray
MessageFactory::getRefStr( const KMime::Message::Ptr
&msg
)
934 QByteArray firstRef
, lastRef
, refStr
, retRefStr
;
937 refStr
= msg
->headerByType("References") ? msg
->headerByType("References")->as7BitString().trimmed() : "";
939 if (refStr
.isEmpty())
940 return msg
->messageID()->as7BitString();
942 i
= refStr
.indexOf('<');
943 j
= refStr
.indexOf('>');
944 firstRef
= refStr
.mid(i
, j
-i
+1);
945 if (!firstRef
.isEmpty())
946 retRefStr
= firstRef
+ ' ';
948 i
= refStr
.lastIndexOf('<');
949 j
= refStr
.lastIndexOf('>');
951 lastRef
= refStr
.mid(i
, j
-i
+1);
952 if (!lastRef
.isEmpty() && lastRef
!= firstRef
)
953 retRefStr
+= lastRef
+ ' ';
955 retRefStr
+= msg
->messageID()->as7BitString();