Waste less space in nested layouts.
[kdepim.git] / messagecomposer / messagefactory.cpp
blob3a1dfe6c94f65967e7377fcdef99a71265de57fb
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"
24 #include "util.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>
41 #include <QTextCodec>
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() )
48 , m_origId( 0 )
49 , m_parentFolderId( 0 )
50 , m_collection( col )
51 , m_replyStrategy( MessageComposer::ReplySmart )
52 , m_quote( true )
53 , m_allowDecryption( true )
54 , m_id ( id )
56 m_origMsg = origMsg;
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;
68 bool replyAll = true;
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() ) {
89 toStr = replyToStr;
90 // use the ReplyAll template only when it's a reply to a mailing list
91 if ( m_mailingListAddresses.isEmpty() )
92 replyAll = false;
94 else if ( !m_mailingListAddresses.isEmpty() ) {
95 toStr = m_mailingListAddresses[0];
97 else {
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();
109 replyAll = false;
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];
118 break;
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
129 toStr = replyToStr;
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(", "));
135 break;
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
145 // addresses
146 for ( QStringList::const_iterator it = m_mailingListAddresses.constBegin();
147 it != m_mailingListAddresses.constEnd();
148 ++it ) {
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] );
164 else {
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() ) {
179 QStringList list;
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 ) ) {
187 ccRecipients += *it;
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];
211 break;
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();
220 ++it ) {
221 recipients = MessageCore::StringUtil::stripAddressFromAddressList( *it, recipients );
223 if ( !recipients.isEmpty() ) {
224 toStr = recipients.join(QString::fromLatin1(", "));
226 else {
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();
235 replyAll = false;
236 break;
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
254 if ( m_quote ) {
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 );
264 else
265 parser.process( m_origMsg, m_collection );
268 applyCharset( msg );
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" ) );
280 msg->assemble();
282 MessageReply reply;
283 reply.msg = msg;
284 reply.replyAll = replyAll;
285 return reply;
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. :)
307 // empty text part
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 );
319 msg->assemble();
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()
325 else {
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" );
335 msg->assemble();
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 );
345 else
346 parser.process( m_origMsg, m_collection );
348 applyCharset( msg );
350 link( msg, m_id, KPIM::MessageStatus::statusForwarded() );
351 msg->assemble();
352 return msg;
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 )
363 msgs << m_origMsg;
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");
381 // set the part
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() );
389 msgPart->assemble();
391 #if 0
392 // THIS HAS TO BE AFTER setCte()!!!!
393 msgPart->setCharset( "" );
394 #else
395 kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO;
396 #endif
397 link( fwdMsg, m_origId, KPIM::MessageStatus::statusForwarded() );
398 attachments << msgPart;
401 applyCharset( msg );
403 link( msg, m_id, KPIM::MessageStatus::statusForwarded() );
404 // msg->assemble();
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() );
413 msg->parse();
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() );
423 return msg;
426 KMime::Message::Ptr MessageFactory::createRedirect( const QString &toStr )
428 if ( !m_origMsg )
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() );
434 msg->parse();
436 uint id = 0;
437 QString strId = msg->headerByType( "X-KMail-Identity" ) ? msg->headerByType( "X-KMail-Identity" )->asUnicodeString().trimmed() : QString::fromLocal8Bit("");
438 if ( !strId.isEmpty())
439 id = strId.toUInt();
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)
458 QString msgIdSuffix;
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 );
494 msg->assemble();
496 link( msg, m_id, KPIM::MessageStatus::statusForwarded() );
497 return msg;
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
521 // ascii only
522 receipt->setBody(str.toLatin1());
523 MessageHelper::setAutomaticFields( receipt );
524 receipt->assemble();
526 return 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
541 #ifndef MDN_DEBUG
542 if ( MessageInfo::instance()->mdnSentState( m_origMsg.get() ) != KMMsgMDNStateUnknown &&
543 MessageInfo::instance()->mdnSentState( m_origMsg.get() ) != KMMsgMDNNone )
544 return KMime::Message::Ptr();
545 #else
546 char st[2]; st[0] = (char)MessageInfo::instance()->mdnSentState( m_origMsg ); st[1] = 0;
547 kDebug() << "mdnSentState() == '" << st <<"'";
548 #endif
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();
569 // Generate message:
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 ) );
584 // text/plain part:
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(
598 finalRecipient,
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 ) {
609 case 1:
610 thirdMsgPart->contentType()->setMimeType( "message/rfc822" );
611 thirdMsgPart->setBody( MessageCore::StringUtil::asSendableString( m_origMsg ) );
612 receipt->addContent( thirdMsgPart );
613 break;
614 case 2:
615 thirdMsgPart->contentType()->setMimeType( "text/rfc822-headers" );
616 thirdMsgPart->setBody( MessageCore::StringUtil::headerAsSendableString( m_origMsg ) );
617 receipt->addContent( thirdMsgPart );
618 break;
619 case 0:
620 default:
621 break;
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 ) );
631 receipt->assemble();
634 kDebug() << "final message:" + receipt->encodedContent();
637 // Set "MDN sent" status:
639 KMMsgMDNSentState state = KMMsgMDNStateUnknown;
640 switch ( d ) {
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 );
650 receipt->assemble();
651 return receipt;
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 );
668 int id = 0;
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() ) );
685 part->assemble();
687 link( fMsg, m_origId, KPIM::MessageStatus::statusForwarded() );
688 digest->addContent( part );
690 digest->assemble();
692 id = m_folderId;
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 )
708 m_origId = 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 )
723 m_quote = quote;
726 void MessageFactory::setAllowDecryption( bool allowD )
728 m_allowDecryption = allowD;
731 void MessageFactory::setTemplate( const QString& templ )
733 m_template = 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
823 // to the request.
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:
829 return true;
831 return false;
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 )
863 QString idString;
864 if ( msg->headerByType("X-KMail-Identity") )
865 idString = msg->headerByType("X-KMail-Identity")->asUnicodeString().trimmed();
866 bool ok = false;
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 ) {
873 id = m_folderId;
875 return id;
879 QString MessageFactory::replaceHeadersInString( const KMime::Message::Ptr &msg, const QString & s )
881 QString result = 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() );
893 int idx = 0;
894 if( ( idx = rxDate.indexIn( result, idx ) ) != -1 ) {
895 result.replace( idx, rxDate.matchedLength(), sDate );
898 idx = 0;
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();
904 return result;
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() ) );
913 if( !codec ) {
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() ) ) );
925 } else {
926 msg->setBody( codec->fromUnicode( QString::fromUtf8( msg->body() ) ) );
932 QByteArray MessageFactory::getRefStr( const KMime::Message::Ptr &msg )
934 QByteArray firstRef, lastRef, refStr, retRefStr;
935 int i, j;
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();
956 return retRefStr;