2 Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
3 Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
4 Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Library General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 #include "attachmentjob.h"
25 #include "globalpart.h"
27 #include "jobbase_p.h"
29 #include "maintextjob.h"
30 #include "multipartjob.h"
32 #include "encryptjob.h"
33 #include "signencryptjob.h"
34 #include "skeletonmessagejob.h"
35 #include "transparentjob.h"
40 #include <klocalizedstring.h>
42 using namespace Message
;
43 using KPIM::AttachmentPart
;
45 class Message::ComposerPrivate
: public JobBasePrivate
48 ComposerPrivate( Composer
*qq
)
49 : JobBasePrivate( qq
)
58 , skeletonMessage( 0 )
64 void doStart(); // slot
66 void skeletonJobFinished( KJob
*job
); // slot
68 void contentJobFinished( KJob
*job
); // slot
69 void contentJobPreCryptFinished( KJob
*job
); // slot
70 void contentJobPreInlineFinished( KJob
*job
); // slot
71 void signBeforeEncryptJobFinished( KJob
*job
); // slot
72 void startEncryptJobs( KMime::Content
* content
);
73 void composeWithLateAttachments( KMime::Message
* headers
, KMime::Content
* content
, AttachmentPart::List parts
, std::vector
<GpgME::Key
> keys
, QStringList recipients
);
74 void attachmentsFinished( KJob
* job
); // slot
76 void composeFinalStep( KMime::Content
* headers
, KMime::Content
* content
);
83 Kleo::CryptoMessageFormat format
;
84 std::vector
<GpgME::Key
> signers
;
85 QList
<QPair
<QStringList
, std::vector
<GpgME::Key
> > > encData
;
87 QList
<KMime::Message::Ptr
> resultMessages
;
89 // Stuff that the application plays with.
90 GlobalPart
*globalPart
;
93 AttachmentPart::List attachmentParts
;
94 // attachments with different sign/encrypt settings from
95 // main message body. added at the end of the process
96 AttachmentPart::List lateAttachmentParts
;
99 // Stuff that we play with.
100 KMime::Message
*skeletonMessage
;
101 KMime::Content
*resultContent
;
103 Q_DECLARE_PUBLIC( Composer
)
106 void ComposerPrivate::init()
110 // We cannot create these in ComposerPrivate's constructor, because
111 // their parent q is not fully constructed at that time.
112 globalPart
= new GlobalPart( q
);
113 infoPart
= new InfoPart( q
);
114 textPart
= new TextPart( q
);
117 void ComposerPrivate::doStart()
119 Q_ASSERT( !started
);
124 void ComposerPrivate::composeStep1()
128 // Create skeleton message (containing headers only; no content).
129 SkeletonMessageJob
*skeletonJob
= new SkeletonMessageJob( infoPart
, globalPart
, q
);
130 QObject::connect( skeletonJob
, SIGNAL(finished(KJob
*)), q
, SLOT(skeletonJobFinished(KJob
*)) );
131 q
->addSubjob( skeletonJob
);
132 skeletonJob
->start();
135 void ComposerPrivate::skeletonJobFinished( KJob
*job
)
138 return; // KCompositeJob takes care of the error.
141 Q_ASSERT( dynamic_cast<SkeletonMessageJob
*>( job
) );
142 SkeletonMessageJob
*sjob
= static_cast<SkeletonMessageJob
*>( job
);
143 // SkeletonMessageJob is a special job creating a Message instead of a Content.
144 Q_ASSERT( skeletonMessage
== 0 );
145 skeletonMessage
= sjob
->message();
146 Q_ASSERT( skeletonMessage
);
147 skeletonMessage
->assemble();
152 void ComposerPrivate::composeStep2()
156 ContentJobBase
*mainJob
= 0;
157 MainTextJob
*mainTextJob
= new MainTextJob( textPart
, q
);
158 if( attachmentParts
.isEmpty() ) {
159 // We have no attachments. Use the content given by the MainTextJob.
160 mainJob
= mainTextJob
;
162 // We have attachments. Create a multipart/mixed content.
163 QMutableListIterator
<AttachmentPart::Ptr
> iter( attachmentParts
);
164 while( iter
.hasNext() ) {
165 AttachmentPart::Ptr part
= iter
.next();
166 kDebug() << "Checking attachment crypto policy..." << part
->isSigned() << part
->isEncrypted();
167 if( !noCrypto
&& ( sign
!= part
->isSigned() || encrypt
!= part
->isEncrypted() ) ) { // different policy
168 kDebug() << "got attachment with different crypto policy!";
169 lateAttachmentParts
.append( part
);
173 MultipartJob
*multipartJob
= new MultipartJob( q
);
174 multipartJob
->setMultipartSubtype( "mixed" );
175 multipartJob
->appendSubjob( mainTextJob
);
176 foreach( AttachmentPart::Ptr part
, attachmentParts
) {
177 multipartJob
->appendSubjob( new AttachmentJob( part
) );
179 mainJob
= multipartJob
;
181 if( sign
&& encrypt
&& format
& Kleo::InlineOpenPGPFormat
) { // needs custom handling--- one SignEncryptJob by itself
182 kDebug() << "sending to sign/enc inline job!";
183 QObject::connect( mainJob
, SIGNAL(finished(KJob
*)), q
, SLOT(contentJobPreInlineFinished(KJob
*)) );
184 } else if( sign
|| encrypt
) {
185 QObject::connect( mainJob
, SIGNAL(finished(KJob
*)), q
, SLOT(contentJobPreCryptFinished(KJob
*)) );
187 QObject::connect( mainJob
, SIGNAL(finished(KJob
*)), q
, SLOT(contentJobFinished(KJob
*)) );
189 q
->addSubjob( mainJob
);
194 void ComposerPrivate::contentJobPreInlineFinished( KJob
*job
)
198 Q_ASSERT( format
& Kleo::InlineOpenPGPFormat
);
199 Q_ASSERT( sign
&& encrypt
); // for safety... we shouldn't be here otherwise
200 Q_ASSERT( dynamic_cast<ContentJobBase
*>( job
) );
201 ContentJobBase
*cjob
= static_cast<ContentJobBase
*>( job
);
203 kDebug() << "creaeting inline signandenc job";
204 if( encData
.size() == 0 ) { // no key data! bail!
205 q
->setErrorText( i18n( "No key data for recipients found." ) );
206 q
->setError( Composer::IncompleteError
);
212 for( int i
= 0; i
< encData
.size(); ++i
) {
213 QPair
<QStringList
, std::vector
<GpgME::Key
> > recipients
= encData
[ i
];
214 kDebug() << "got first list of recipients:" << recipients
.first
;
215 SignEncryptJob
* seJob
= new SignEncryptJob( q
);
216 seJob
->setContent( cjob
->content() );
217 seJob
->setCryptoMessageFormat( format
);
218 seJob
->setEncryptionKeys( recipients
.second
);
219 seJob
->setSigningKeys( signers
);
220 seJob
->setRecipients( recipients
.first
);
222 QObject::connect( seJob
, SIGNAL( finished( KJob
* ) ), q
, SLOT( contentJobFinished( KJob
* ) ) );
224 q
->addSubjob( seJob
);
230 void ComposerPrivate::contentJobPreCryptFinished( KJob
*job
)
234 // we're signing or encrypting or both, so add an additional job to the process
235 Q_ASSERT( dynamic_cast<ContentJobBase
*>( job
) );
236 ContentJobBase
*cjob
= static_cast<ContentJobBase
*>( job
);
240 SignJob
* sJob
= new SignJob( q
);
241 sJob
->setContent( cjob
->content() );
242 sJob
->setCryptoMessageFormat( format
);
243 sJob
->setSigningKeys( signers
);
246 QObject::connect( sJob
, SIGNAL( finished( KJob
* ) ), q
, SLOT( signBeforeEncryptJobFinished( KJob
* ) ) );
248 QObject::connect( sJob
, SIGNAL( finished( KJob
* ) ), q
, SLOT( contentJobFinished( KJob
* ) ) );
250 q
->addSubjob( sJob
);
253 } else if( encrypt
) {
254 // just encrypting, so setup the jobs directly
255 startEncryptJobs( cjob
->content() );
260 void ComposerPrivate::signBeforeEncryptJobFinished( KJob
* job
)
264 return; // KCompositeJob takes care of the error.
267 Q_ASSERT( dynamic_cast<ContentJobBase
*>( job
) );
268 ContentJobBase
*cjob
= static_cast<ContentJobBase
*>( job
);
270 // cjob holds the signed content, now we encrypt per recipient
271 startEncryptJobs( cjob
->content() );
275 void ComposerPrivate::startEncryptJobs( KMime::Content
* content
) {
278 // each SplitInfo holds a list of recipients/keys, if there is more than
279 // one item in it then it means there are secondary recipients that need
280 // different messages w/ clean headers
281 kDebug() << "starting enc jobs";
282 kDebug() << "format:" << format
;
283 kDebug() << "enc data:" << encData
.size();
285 if( encData
.size() == 0 ) { // no key data! bail!
286 q
->setErrorText( i18n( "No key data for recipients found." ) );
287 q
->setError( Composer::IncompleteError
);
292 for( int i
= 0; i
< encData
.size(); ++i
) {
293 QPair
<QStringList
, std::vector
<GpgME::Key
> > recipients
= encData
[ i
];
294 kDebug() << "got first list of recipients:" << recipients
.first
;
295 EncryptJob
* eJob
= new EncryptJob( q
);
296 eJob
->setContent( content
);
297 eJob
->setCryptoMessageFormat( format
);
298 eJob
->setEncryptionKeys( recipients
.second
);
299 eJob
->setRecipients( recipients
.first
);
301 QObject::connect( eJob
, SIGNAL( finished( KJob
* ) ), q
, SLOT( contentJobFinished( KJob
* ) ) );
303 q
->addSubjob( eJob
);
309 void ComposerPrivate::contentJobFinished( KJob
*job
)
314 return; // KCompositeJob takes care of the error.
316 kDebug() << "composing final message";
318 KMime::Message
* headers
;
319 KMime::Content
* resultContent
;
320 std::vector
<GpgME::Key
> keys
;
321 QStringList recipients
;
323 Q_ASSERT( dynamic_cast<ContentJobBase
*>( job
) == static_cast<ContentJobBase
*>( job
) );
324 ContentJobBase
* contentJob
= static_cast<ContentJobBase
*>( job
);
326 // create the final headers and body,
327 // taking into account secondary recipients for encryption
328 if( encData
.size() > 1 ) { // crypto job with secondary recipients..
329 Q_ASSERT( dynamic_cast<AbstractEncryptJob
*>( job
) ); // we need to get the recipients for this job
330 AbstractEncryptJob
* eJob
= dynamic_cast<AbstractEncryptJob
*>( job
);
332 keys
= eJob
->encryptionKeys();
333 recipients
= eJob
->recipients();
335 resultContent
= contentJob
->content(); // content() comes from superclass
336 headers
= new KMime::Message
;
337 headers
->setHeader( skeletonMessage
->from() );
338 headers
->setHeader( skeletonMessage
->to() );
339 headers
->setHeader( skeletonMessage
->subject() );
341 KMime::Headers::Bcc
*bcc
= new KMime::Headers::Bcc( headers
);
342 foreach( const QString
&a
, eJob
->recipients() ) {
343 KMime::Types::Mailbox address
;
344 address
.fromUnicodeString( a
);
345 bcc
->addAddress( address
);
348 kDebug() << "got one of multiple messages sending to:" << bcc
->asUnicodeString();
349 kDebug() << "sending to recipients:" << recipients
;
350 headers
->setHeader( bcc
);
352 } else { // just use the saved headers from before
353 if( encData
.size() > 0 ) {
354 kDebug() << "setting enc data:" << encData
[ 0 ].first
<< "with num keys:" << encData
[ 0 ].second
.size();
355 keys
= encData
[ 0 ].second
;
356 recipients
= encData
[ 0 ].first
;
359 headers
= skeletonMessage
;
360 resultContent
= contentJob
->content();
362 // manually remove the subjob so we can check if we have any left later
363 q
->removeSubjob( job
);
365 if( lateAttachmentParts
.isEmpty() ) {
366 composeFinalStep( headers
, resultContent
);
368 composeWithLateAttachments( headers
, resultContent
, lateAttachmentParts
, keys
, recipients
);
373 void ComposerPrivate::composeWithLateAttachments( KMime::Message
* headers
, KMime::Content
* content
, AttachmentPart::List parts
, std::vector
<GpgME::Key
> keys
, QStringList recipients
)
377 MultipartJob
* multiJob
= new MultipartJob( q
);
378 multiJob
->setMultipartSubtype( "mixed" );
380 // wrap the content into a job for the multijob to handle it
381 TransparentJob
* tJob
= new TransparentJob( q
);
382 tJob
->setContent( content
);
383 multiJob
->appendSubjob( tJob
);
384 multiJob
->setExtraContent( headers
);
386 kDebug() << "attachment encr key size:" << keys
.size() << recipients
;
388 // operate correctly on each attachment that has a different crypto policy than body.
389 foreach( AttachmentPart::Ptr attachment
, parts
) {
390 AttachmentJob
* attachJob
= new AttachmentJob( attachment
, q
);
392 kDebug() << "got a late attachment";
393 if( attachment
->isSigned() ) {
394 kDebug() << "adding signjob for late attachment";
395 SignJob
* sJob
= new SignJob( q
);
396 sJob
->setContent( 0 );
397 sJob
->setCryptoMessageFormat( format
);
398 sJob
->setSigningKeys( signers
);
400 sJob
->appendSubjob( attachJob
);
401 if( attachment
->isEncrypted() ) {
402 kDebug() << "adding sign + encrypt job for late attachment";
403 EncryptJob
* eJob
= new EncryptJob( q
);
404 eJob
->setCryptoMessageFormat( format
);
405 eJob
->setEncryptionKeys( keys
);
406 eJob
->setRecipients( recipients
);
408 eJob
->appendSubjob( sJob
);
410 multiJob
->appendSubjob( eJob
);
412 kDebug() << "Just signing late attachment";
413 multiJob
->appendSubjob( sJob
);
415 } else if( attachment
->isEncrypted() ) { // only encryption
416 kDebug() << "just encrypting late attachment";
417 EncryptJob
* eJob
= new EncryptJob( q
);
418 eJob
->setCryptoMessageFormat( format
);
419 eJob
->setEncryptionKeys( keys
);
420 eJob
->setRecipients( recipients
);
422 eJob
->appendSubjob( attachJob
);
423 multiJob
->appendSubjob( eJob
);
425 kDebug() << "attaching plain non-crypto attachment";
426 AttachmentJob
* attachJob
= new AttachmentJob( attachment
, q
);
427 multiJob
->appendSubjob( attachJob
);
431 QObject::connect( multiJob
, SIGNAL( finished( KJob
* ) ), q
, SLOT( attachmentsFinished( KJob
* ) ) );
433 q
->addSubjob( multiJob
);
437 void ComposerPrivate::attachmentsFinished( KJob
* job
) {
441 return; // KCompositeJob takes care of the error.
443 kDebug() << "composing final message with late attachments";
445 Q_ASSERT( dynamic_cast<ContentJobBase
*>( job
) );
446 ContentJobBase
* contentJob
= static_cast<ContentJobBase
*>( job
);
448 KMime::Content
* content
= contentJob
->content();
449 KMime::Content
* headers
= contentJob
->extraContent();
451 q
->removeSubjob( job
);
452 composeFinalStep( headers
, content
);
456 void ComposerPrivate::composeFinalStep( KMime::Content
* headers
, KMime::Content
* content
)
462 QByteArray allData
= headers
->head() + content
->encodedContent();
463 KMime::Message::Ptr
resultMessage( new KMime::Message
);
464 resultMessage
->setContent( allData
);
465 resultMessage
->parse(); // Not strictly necessary.
466 resultMessages
.append( resultMessage
);
468 kDebug() << "still have subjobs:" << q
->hasSubjobs() << "num:" << q
->subjobs().size();
469 if( !q
->hasSubjobs() ) {
475 Composer::Composer( QObject
*parent
)
476 : JobBase( *new ComposerPrivate( this ), parent
)
482 Composer::~Composer()
486 QList
<KMime::Message::Ptr
> Composer::resultMessages() const
488 Q_D( const Composer
);
489 Q_ASSERT( d
->finished
);
490 Q_ASSERT( !error() );
491 QList
<KMime::Message::Ptr
> results
= d
->resultMessages
;
495 GlobalPart
*Composer::globalPart()
498 return d
->globalPart
;
501 InfoPart
* Composer::infoPart()
507 TextPart
*Composer::textPart()
513 AttachmentPart::List
Composer::attachmentParts()
516 return d
->attachmentParts
;
519 void Composer::addAttachmentPart( AttachmentPart::Ptr part
)
522 Q_ASSERT( !d
->started
);
523 Q_ASSERT( !d
->attachmentParts
.contains( part
) );
524 d
->attachmentParts
.append( part
);
527 void Composer::addAttachmentParts( const AttachmentPart::List
&parts
)
529 foreach( AttachmentPart::Ptr part
, parts
) {
530 addAttachmentPart( part
);
534 void Composer::removeAttachmentPart( AttachmentPart::Ptr part
)
537 Q_ASSERT( !d
->started
);
538 if( d
->attachmentParts
.contains( part
) ) {
539 d
->attachmentParts
.removeAll( part
);
541 kError() << "Unknown attachment part" << part
;
547 void Composer::setSignAndEncrypt( const bool doSign
, const bool doEncrypt
)
551 d
->encrypt
= doEncrypt
;
555 void Composer::setMessageCryptoFormat( Kleo::CryptoMessageFormat format
)
562 void Composer::setSigningKeys( std::vector
<GpgME::Key
>& signers
)
566 d
->signers
= signers
;
569 void Composer::setEncryptionKeys( QList
<QPair
<QStringList
, std::vector
<GpgME::Key
> > > encData
)
573 d
->encData
= encData
;
576 void Composer::setNoCrypto(bool noCrypto
)
580 d
->noCrypto
= noCrypto
;
584 void Composer::start()
586 QTimer::singleShot( 0, this, SLOT(doStart()) );
589 #include "composer.moc"