1 /* -*- mode: C++; c-file-style: "gnu" -*-
4 This file is part of KMail, the KDE mail client.
5 Copyright (c) 2003 Marc Mutz <mutz@kde.org>
6 Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
7 Copyright (c) 2009 Andras Mantia <andras@kdab.net>
9 KMail is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License, version 2, as
11 published by the Free Software Foundation.
13 KMail is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 In addition, as a special exception, the copyright holders give
23 permission to link the code of this program with any edition of
24 the Qt library by Trolltech AS, Norway (or with modified versions
25 of Qt that use the same license as Qt), and distribute linked
26 combinations including the two. You must obey the GNU General
27 Public License in all respects for all of the code used other than
28 Qt. If you modify this file, you may extend this exception to
29 your version of the file, but you are not obligated to do so. If
30 you do not wish to do so, delete this exception statement from
34 // MessageViewer includes
35 #include <config-messageviewer.h>
37 #include "objecttreeparser.h"
39 #include "objecttreeparser_p.h"
40 #include "objecttreesourceif.h"
41 #include "autoqpointer.h"
43 #include "partmetadata.h"
44 #include "attachmentstrategy.h"
45 #include "interfaces/htmlwriter.h"
46 #include "htmlstatusbar.h"
47 #include "csshelper.h"
48 #include "bodypartformatter.h"
49 #include "bodypartformatterfactory.h"
50 #include "partnodebodypart.h"
51 #include "interfaces/bodypartformatter.h"
52 #include "globalsettings.h"
54 #include "kleojobexecutor.h"
55 #include "nodehelper.h"
56 #include "iconnamecache.h"
57 #include "htmlquotecolorer.h"
58 #include "chiasmuskeyselector.h"
61 #include <messagecore/stringutil.h>
62 #include <kleo/specialjob.h>
63 #include <kleo/cryptobackendfactory.h>
64 #include <kleo/decryptverifyjob.h>
65 #include <kleo/verifydetachedjob.h>
66 #include <kleo/verifyopaquejob.h>
67 #include <kleo/keylistjob.h>
68 #include <kleo/importjob.h>
70 #include <libkpgp/kpgpblock.h>
71 #include <libkpgp/kpgp.h>
73 // KDEPIMLIBS includes
74 #include <kpimutils/email.h>
75 #include <kpimutils/linklocator.h>
76 #include <gpgme++/importresult.h>
77 #include <gpgme++/decryptionresult.h>
78 #include <gpgme++/key.h>
79 #include <gpgme++/keylistresult.h>
81 #include <kmime/kmime_message.h>
82 #include <kmime/kmime_util.h>
87 #include <kmimetype.h>
89 #include <ktemporaryfile.h>
90 #include <kstandarddirs.h>
91 #include <kiconloader.h>
93 #include <kconfiggroup.h>
97 #include <QApplication>
100 #include <QTextCodec>
101 #include <QByteArray>
109 #include <sys/stat.h>
110 #include <sys/types.h>
113 #include <messagecore/nodehelper.h>
115 using KPIMUtils::LinkLocator
;
116 using namespace MessageViewer
;
117 using namespace MessageCore
;
119 // A small class that eases temporary CryptPlugWrapper changes:
120 class ObjectTreeParser::CryptoProtocolSaver
{
121 ObjectTreeParser
* otp
;
122 const Kleo::CryptoBackend::Protocol
* protocol
;
124 CryptoProtocolSaver( ObjectTreeParser
* _otp
, const Kleo::CryptoBackend::Protocol
* _w
)
125 : otp( _otp
), protocol( _otp
? _otp
->cryptoProtocol() : 0 )
128 otp
->setCryptoProtocol( _w
);
131 ~CryptoProtocolSaver() {
133 otp
->setCryptoProtocol( protocol
);
137 ObjectTreeParser::ObjectTreeParser( const ObjectTreeParser
*topLevelParser
,
138 bool showOnlyOneMimePart
, bool keepEncryptions
,
139 bool includeSignatures
,
140 const AttachmentStrategy
* strategy
)
141 : mSource( topLevelParser
->mSource
),
142 mNodeHelper( topLevelParser
->mNodeHelper
),
143 mTopLevelContent( topLevelParser
->mTopLevelContent
),
144 mCryptoProtocol( topLevelParser
->mCryptoProtocol
),
145 mShowOnlyOneMimePart( showOnlyOneMimePart
),
146 mKeepEncryptions( keepEncryptions
),
147 mIncludeSignatures( includeSignatures
),
148 mHasPendingAsyncJobs( false ),
149 mAllowAsync( topLevelParser
->mAllowAsync
),
150 mShowRawToltecMail( false ),
151 mAttachmentStrategy( strategy
)
156 ObjectTreeParser::ObjectTreeParser( ObjectTreeSourceIf
*source
,
157 MessageViewer::NodeHelper
* nodeHelper
,
158 const Kleo::CryptoBackend::Protocol
* protocol
,
159 bool showOnlyOneMimePart
, bool keepEncryptions
,
160 bool includeSignatures
,
161 const AttachmentStrategy
* strategy
)
163 mNodeHelper( nodeHelper
),
164 mTopLevelContent( 0 ),
165 mCryptoProtocol( protocol
),
166 mShowOnlyOneMimePart( showOnlyOneMimePart
),
167 mKeepEncryptions( keepEncryptions
),
168 mIncludeSignatures( includeSignatures
),
169 mHasPendingAsyncJobs( false ),
170 mAllowAsync( false ),
171 mShowRawToltecMail( false ),
172 mAttachmentStrategy( strategy
)
177 void ObjectTreeParser::init()
180 if ( !attachmentStrategy() )
181 mAttachmentStrategy
= mSource
->attachmentStrategy();
183 if ( !mNodeHelper
) {
184 mNodeHelper
= new NodeHelper();
185 mDeleteNodeHelper
= true;
187 mDeleteNodeHelper
= false;
191 ObjectTreeParser::ObjectTreeParser( const ObjectTreeParser
& other
)
192 : mSource( other
.mSource
),
193 mNodeHelper( other
.nodeHelper() ), //TODO(Andras) hm, review what happens if mDeleteNodeHelper was true in the source
194 mTopLevelContent( other
.mTopLevelContent
),
195 mCryptoProtocol( other
.cryptoProtocol() ),
196 mShowOnlyOneMimePart( other
.showOnlyOneMimePart() ),
197 mKeepEncryptions( other
.keepEncryptions() ),
198 mIncludeSignatures( other
.includeSignatures() ),
199 mHasPendingAsyncJobs( other
.hasPendingAsyncJobs() ),
200 mAllowAsync( other
.allowAsync() ),
201 mAttachmentStrategy( other
.attachmentStrategy() ),
202 mDeleteNodeHelper( false ) // TODO see above
207 ObjectTreeParser::~ObjectTreeParser()
209 if ( mDeleteNodeHelper
) {
215 void ObjectTreeParser::createAndParseTempNode( KMime::Content
* parentNode
, const char* content
, const char* cntDesc
)
217 // kDebug() << "CONTENT: " << QByteArray( content ).left( 100 ) << " CNTDESC: " << cntDesc;
219 KMime::Content
*newNode
= new KMime::Content();
220 newNode
->setContent( KMime::CRLFtoLF( content
) );
223 kDebug() << "MEDIATYPE: " << newNode->contentType()->mediaType() << newNode->contentType()->mimeType() ;
224 kDebug() << "DECODEDCONTENT: " << newNode->decodedContent().left(400);
225 kDebug() << "ENCODEDCONTENT: " << newNode->encodedContent().left(400);
226 kDebug() << "BODY: " << newNode->body().left(400);
229 if ( !newNode
->head().isEmpty() ) {
230 newNode
->contentDescription()->from7BitString( cntDesc
);
232 mNodeHelper
->attachExtraContent( parentNode
, newNode
);
234 ObjectTreeParser
otp( this );
235 otp
.parseObjectTreeInternal( newNode
);
236 mRawReplyString
+= otp
.rawReplyString();
237 mTextualContent
+= otp
.textualContent();
238 if ( !otp
.textualContentCharset().isEmpty() )
239 mTextualContentCharset
= otp
.textualContentCharset();
243 //-----------------------------------------------------------------------------
245 void ObjectTreeParser::parseObjectTree( KMime::Content
* node
)
247 mTopLevelContent
= node
;
248 parseObjectTreeInternal( node
);
251 void ObjectTreeParser::parseObjectTreeInternal( KMime::Content
* node
)
256 // reset pending async jobs state (we'll rediscover pending jobs as we go)
257 mHasPendingAsyncJobs
= false;
259 // reset "processed" flags for...
260 if ( showOnlyOneMimePart() ) {
261 // ... this node and all descendants
262 mNodeHelper
->setNodeUnprocessed( node
, false );
263 if ( MessageCore::NodeHelper::firstChild( node
) ) {
264 mNodeHelper
->setNodeUnprocessed( node
, true );
266 } else if ( !node
->parent() ) {
267 // ...this node and all it's siblings and descendants
268 mNodeHelper
->setNodeUnprocessed( node
, true );
271 // Make sure the whole content is relative, so that nothing is painted over the header
272 // if a malicious message uses absolute positioning.
273 // Also force word wrapping, which is useful for printing, see https://issues.kolab.org/issue3992.
274 bool isRoot
= node
->isTopLevel();
275 if ( isRoot
&& htmlWriter() )
276 htmlWriter()->queue( "<div style=\"position: relative; word-wrap: break-word\">\n" );
278 for( ; node
; node
= MessageCore::NodeHelper::nextSibling( node
) )
280 if ( mNodeHelper
->nodeProcessed( node
) ) {
284 ProcessResult
processResult( mNodeHelper
);
286 KMime::ContentIndex contentIndex
= node
->index();
287 if ( htmlWriter() /*&& contentIndex.isValid()*/ )
288 htmlWriter()->queue( QString::fromLatin1("<a name=\"att%1\"></a>").arg( contentIndex
.toString() ) );
290 QByteArray
mediaType( "text" );
291 QByteArray
subType( "plain" );
292 if ( node
->contentType( false ) && !node
->contentType()->mediaType().isEmpty() &&
293 !node
->contentType()->subType().isEmpty() ) {
294 mediaType
= node
->contentType()->mediaType();
295 subType
= node
->contentType()->subType();
298 // First, try if an external plugin can handle this MIME part
299 if ( const Interface::BodyPartFormatter
* formatter
300 = BodyPartFormatterFactory::instance()->createFor( mediaType
, subType
) ) {
301 PartNodeBodyPart
part( mTopLevelContent
, node
, mNodeHelper
, codecFor( node
) );
302 // Set the default display strategy for this body part relying on the
303 // identity of Interface::BodyPart::Display and AttachmentStrategy::Display
304 part
.setDefaultDisplay( (Interface::BodyPart::Display
) attachmentStrategy()->defaultDisplay( node
) );
306 writeAttachmentMarkHeader( node
);
307 mNodeHelper
->setNodeDisplayedEmbedded( node
, true );
309 QObject
* asyncResultObserver
= allowAsync() ? mSource
->sourceObject() : 0;
310 const Interface::BodyPartFormatter::Result result
= formatter
->format( &part
, htmlWriter(), asyncResultObserver
);
312 case Interface::BodyPartFormatter::AsIcon
:
313 processResult
.setNeverDisplayInline( true );
314 mNodeHelper
->setNodeDisplayedEmbedded( node
, false );
316 case Interface::BodyPartFormatter::Failed
:
317 defaultHandling( node
, processResult
);
319 case Interface::BodyPartFormatter::Ok
:
320 case Interface::BodyPartFormatter::NeedContent
:
321 // FIXME: incomplete content handling
325 writeAttachmentMarkFooter();
327 // No external plugin can handle the MIME part, handle it internally
329 const BodyPartFormatter
* bpf
330 = BodyPartFormatter::createFor( mediaType
, subType
);
332 kFatal() << "THIS SHOULD NO LONGER HAPPEN:" << mediaType
<< '/' << subType
;
334 writeAttachmentMarkHeader( node
);
335 if ( bpf
&& !bpf
->process( this, node
, processResult
) ) {
336 defaultHandling( node
, processResult
);
338 writeAttachmentMarkFooter();
340 mNodeHelper
->setNodeProcessed( node
, false);
342 // adjust signed/encrypted flags if inline PGP was found
343 processResult
.adjustCryptoStatesOfNode( node
);
345 if ( showOnlyOneMimePart() )
349 if ( isRoot
&& htmlWriter() )
350 htmlWriter()->queue( "</div>\n" );
353 void ObjectTreeParser::defaultHandling( KMime::Content
* node
, ProcessResult
& result
) {
354 // ### (mmutz) default handling should go into the respective
355 // ### bodypartformatters.
356 if ( !htmlWriter() ) {
357 kWarning() << "no htmlWriter()";
361 // always show images in multipart/related when showing in html, not with an additional icon
362 if ( result
.isImage() && node
->parent() &&
363 node
->parent()->contentType()->subType() == "related" && mSource
->htmlMail() && !showOnlyOneMimePart() ) {
364 QString fileName
= mNodeHelper
->writeNodeToTempFile( node
);
365 QString href
= "file:///" + fileName
;
366 QByteArray cid
= node
->contentID()->identifier();
367 htmlWriter()->embedPart( cid
, href
);
368 nodeHelper()->setNodeDisplayedEmbedded( node
, true );
372 const AttachmentStrategy
*const as
= attachmentStrategy();
373 if ( as
&& as
->defaultDisplay( node
) == AttachmentStrategy::None
&&
374 !showOnlyOneMimePart() &&
375 node
->parent() /* message is not an attachment */ ) {
376 mNodeHelper
->setNodeDisplayedHidden( node
, true );
381 if ( !result
.neverDisplayInline() )
383 asIcon
= as
->defaultDisplay( node
) == AttachmentStrategy::AsIcon
;
385 // Show it inline if showOnlyOneMimePart(), which means the user clicked the image
386 // in the message structure viewer manually, and therefore wants to see the full image
387 if ( result
.isImage() && showOnlyOneMimePart() && !result
.neverDisplayInline() )
390 // neither image nor text -> show as icon
391 if ( !result
.isImage()
392 && !node
->contentType()->isText() )
395 /*FIXME(Andras) port it
396 // if the image is not complete do not try to show it inline
397 if ( result.isImage() && !node->msgPart().isComplete() )
402 if ( !( as
&& as
->defaultDisplay( node
) == AttachmentStrategy::None
) ||
403 showOnlyOneMimePart() ) {
404 // Write the node as icon only
405 writePartIcon( node
);
407 mNodeHelper
->setNodeDisplayedHidden( node
, true );
409 } else if ( result
.isImage() ) {
411 mNodeHelper
->setNodeDisplayedEmbedded( node
, true );
412 writePartIcon( node
, true );
414 mNodeHelper
->setNodeDisplayedEmbedded( node
, true );
415 writeBodyString( node
->decodedContent(),
416 NodeHelper::fromAsString( node
),
417 codecFor( node
), result
, false );
422 void ProcessResult::adjustCryptoStatesOfNode( KMime::Content
* node
) const {
423 if ( ( inlineSignatureState() != KMMsgNotSigned
) ||
424 ( inlineEncryptionState() != KMMsgNotEncrypted
) ) {
425 mNodeHelper
->setSignatureState( node
, inlineSignatureState() );
426 mNodeHelper
->setEncryptionState( node
, inlineEncryptionState() );
434 static int signatureToStatus( const GpgME::Signature
&sig
)
436 switch ( sig
.status().code() ) {
437 case GPG_ERR_NO_ERROR
:
438 return GPGME_SIG_STAT_GOOD
;
439 case GPG_ERR_BAD_SIGNATURE
:
440 return GPGME_SIG_STAT_BAD
;
441 case GPG_ERR_NO_PUBKEY
:
442 return GPGME_SIG_STAT_NOKEY
;
443 case GPG_ERR_NO_DATA
:
444 return GPGME_SIG_STAT_NOSIG
;
445 case GPG_ERR_SIG_EXPIRED
:
446 return GPGME_SIG_STAT_GOOD_EXP
;
447 case GPG_ERR_KEY_EXPIRED
:
448 return GPGME_SIG_STAT_GOOD_EXPKEY
;
450 return GPGME_SIG_STAT_ERROR
;
454 bool ObjectTreeParser::writeOpaqueOrMultipartSignedData( KMime::Content
* data
,
455 KMime::Content
& sign
,
456 const QString
& fromAddress
,
458 QByteArray
* cleartextData
,
459 const std::vector
<GpgME::Signature
> & paramSignatures
,
462 // kDebug() << "DECRYPT" << data;
463 bool bIsOpaqueSigned
= false;
464 enum { NO_PLUGIN
, NOT_INITIALIZED
, CANT_VERIFY_SIGNATURES
}
465 cryptPlugError
= NO_PLUGIN
;
467 const Kleo::CryptoBackend::Protocol
* cryptProto
= cryptoProtocol();
469 QString cryptPlugLibName
;
470 QString cryptPlugDisplayName
;
472 cryptPlugLibName
= cryptProto
->name();
473 cryptPlugDisplayName
= cryptProto
->displayName();
476 #ifdef DEBUG_SIGNATURE
479 kDebug() << "showing OpenPGP (Encrypted+Signed) data";
482 kDebug() << "processing Multipart Signed data";
484 kDebug() << "processing Opaque Signed data";
487 if ( doCheck
&& cryptProto
) {
488 //kDebug() << "going to call CRYPTPLUG" << cryptPlugLibName;
492 QByteArray cleartext
;
493 QByteArray signaturetext
;
495 if ( doCheck
&& cryptProto
) {
497 cleartext
= data
->encodedContent();
498 #ifdef DEBUG_SIGNATURE
499 kDebug() << "ClearText : " << cleartext
;
501 dumpToFile( "dat_01_reader_signedtext_before_canonicalization",
502 cleartext
.data(), cleartext
.length() );
504 // replace simple LFs by CRLSs
505 // according to RfC 2633, 3.1.1 Canonicalization
506 kDebug() << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)";
508 cleartext
= KMime::LFtoCRLF( cleartext
);
509 #ifdef DEBUG_SIGNATURE
510 kDebug() << " done.";
514 dumpToFile( "dat_02_reader_signedtext_after_canonicalization",
515 cleartext
.data(), cleartext
.length() );
517 signaturetext
= sign
.decodedContent();
518 dumpToFile( "dat_03_reader.sig", signaturetext
.data(),
519 signaturetext
.size() );
522 std::vector
<GpgME::Signature
> signatures
;
524 signatures
= paramSignatures
;
526 PartMetaData messagePart
;
527 messagePart
.isSigned
= true;
528 messagePart
.technicalProblem
= ( cryptProto
== 0 );
529 messagePart
.isGoodSignature
= false;
530 messagePart
.isEncrypted
= false;
531 messagePart
.isDecryptable
= false;
532 messagePart
.keyTrust
= Kpgp::KPGP_VALIDITY_UNKNOWN
;
533 messagePart
.status
= i18n("Wrong Crypto Plug-In.");
534 messagePart
.status_code
= GPGME_SIG_STAT_NONE
;
538 if ( doCheck
&& cryptProto
) {
539 #ifdef DEBUG_SIGNATURE
540 kDebug() << "tokoe: doCheck and cryptProto";
542 GpgME::VerificationResult result
;
543 if ( data
) { // detached
544 #ifdef DEBUG_SIGNATURE
545 kDebug() << "tokoe: is detached signature";
547 const VerifyDetachedBodyPartMemento
* m
548 = dynamic_cast<VerifyDetachedBodyPartMemento
*>( mNodeHelper
->bodyPartMemento( &sign
, "verifydetached" ) );
550 #ifdef DEBUG_SIGNATURE
551 kDebug() << "tokoe: no memento available";
553 Kleo::VerifyDetachedJob
* job
= cryptProto
->verifyDetachedJob();
555 cryptPlugError
= CANT_VERIFY_SIGNATURES
;
556 // PENDING(marc) cryptProto = 0 here?
558 QByteArray plainData
= cleartext
;
559 VerifyDetachedBodyPartMemento
* newM
560 = new VerifyDetachedBodyPartMemento( job
, cryptProto
->keyListJob(), signaturetext
, plainData
);
561 if ( allowAsync() ) {
562 #ifdef DEBUG_SIGNATURE
563 kDebug() << "tokoe: allowAsync";
565 QObject::connect( newM
, SIGNAL(update(MessageViewer::Viewer::UpdateMode
)),
566 mSource
->sourceObject(), SLOT(update(MessageViewer::Viewer::UpdateMode
)) );
567 if ( newM
->start() ) {
568 #ifdef DEBUG_SIGNATURE
569 kDebug() << "tokoe: new memento started";
571 messagePart
.inProgress
= true;
572 mHasPendingAsyncJobs
= true;
580 mNodeHelper
->setBodyPartMemento( &sign
, "verifydetached", newM
);
582 } else if ( m
->isRunning() ) {
583 #ifdef DEBUG_SIGNATURE
584 kDebug() << "tokoe: memento is running";
586 messagePart
.inProgress
= true;
587 mHasPendingAsyncJobs
= true;
592 #ifdef DEBUG_SIGNATURE
593 kDebug() << "tokoe: memento finished, assign result";
595 result
= m
->verifyResult();
596 messagePart
.auditLogError
= m
->auditLogError();
597 messagePart
.auditLog
= m
->auditLogAsHtml();
598 key
= m
->signingKey();
601 #ifdef DEBUG_SIGNATURE
602 kDebug() << "tokoe: is opaque signature";
604 const VerifyOpaqueBodyPartMemento
* m
605 = dynamic_cast<VerifyOpaqueBodyPartMemento
*>( mNodeHelper
->bodyPartMemento( &sign
, "verifyopaque" ) );
607 #ifdef DEBUG_SIGNATURE
608 kDebug() << "tokoe: no memento available";
610 Kleo::VerifyOpaqueJob
* job
= cryptProto
->verifyOpaqueJob();
612 cryptPlugError
= CANT_VERIFY_SIGNATURES
;
613 // PENDING(marc) cryptProto = 0 here?
615 VerifyOpaqueBodyPartMemento
* newM
616 = new VerifyOpaqueBodyPartMemento( job
, cryptProto
->keyListJob(), signaturetext
);
617 if ( allowAsync() ) {
618 #ifdef DEBUG_SIGNATURE
619 kDebug() << "tokoe: allowAsync";
621 QObject::connect( newM
, SIGNAL(update(MessageViewer::Viewer::UpdateMode
)), mSource
->sourceObject(),
622 SLOT(update(MessageViewer::Viewer::UpdateMode
)) );
623 if ( newM
->start() ) {
624 #ifdef DEBUG_SIGNATURE
625 kDebug() << "tokoe: new memento started";
627 messagePart
.inProgress
= true;
628 mHasPendingAsyncJobs
= true;
636 mNodeHelper
->setBodyPartMemento( &sign
, "verifyopaque", newM
);
638 } else if ( m
->isRunning() ) {
639 #ifdef DEBUG_SIGNATURE
640 kDebug() << "tokoe: memento is running";
642 messagePart
.inProgress
= true;
643 mHasPendingAsyncJobs
= true;
648 #ifdef DEBUG_SIGNATURE
649 kDebug() << "tokoe: memento finished, assign result";
651 result
= m
->verifyResult();
652 cleartext
= m
->plainText();
653 messagePart
.auditLogError
= m
->auditLogError();
654 messagePart
.auditLog
= m
->auditLogAsHtml();
655 key
= m
->signingKey();
658 std::stringstream ss
;
660 #ifdef DEBUG_SIGNATURE
661 kDebug() << ss
.str().c_str();
663 signatures
= result
.signatures();
666 messagePart
.auditLogError
= GpgME::Error( GPG_ERR_NOT_IMPLEMENTED
);
668 #ifdef DEBUG_SIGNATURE
670 kDebug() << "returned from CRYPTPLUG";
673 // ### only one signature supported
674 if ( !signatures
.empty() ) {
675 #ifdef DEBUG_SIGNATURE
676 kDebug() << "\nFound signature";
678 GpgME::Signature signature
= signatures
.front();
680 messagePart
.status_code
= signatureToStatus( signature
);
681 messagePart
.status
= QString::fromLocal8Bit( signature
.status().asString() );
682 for ( uint i
= 1; i
< signatures
.size(); ++i
) {
683 if ( signatureToStatus( signatures
[i
] ) != messagePart
.status_code
) {
684 messagePart
.status_code
= GPGME_SIG_STAT_DIFF
;
685 messagePart
.status
= i18n("Different results for signatures");
688 if ( messagePart
.status_code
& GPGME_SIG_STAT_GOOD
)
689 messagePart
.isGoodSignature
= true;
691 // save extended signature status flags
692 messagePart
.sigSummary
= signature
.summary();
695 messagePart
.keyId
= key
.keyID();
696 if ( messagePart
.keyId
.isEmpty() )
697 messagePart
.keyId
= signature
.fingerprint();
698 // ### Ugh. We depend on two enums being in sync:
699 messagePart
.keyTrust
= (Kpgp::Validity
)signature
.validity();
700 if ( key
.numUserIDs() > 0 && key
.userID( 0 ).id() )
701 messagePart
.signer
= Kleo::DN( key
.userID( 0 ).id() ).prettyDN();
702 for ( uint iMail
= 0; iMail
< key
.numUserIDs(); ++iMail
) {
703 // The following if /should/ always result in TRUE but we
704 // won't trust implicitely the plugin that gave us these data.
705 if ( key
.userID( iMail
).email() ) {
706 QString email
= QString::fromUtf8( key
.userID( iMail
).email() );
707 // ### work around gpgme 0.3.x / cryptplug bug where the
708 // ### email addresses are specified as angle-addr, not addr-spec:
709 if ( email
.startsWith( '<' ) && email
.endsWith( '>' ) )
710 email
= email
.mid( 1, email
.length() - 2 );
711 if ( !email
.isEmpty() )
712 messagePart
.signerMailAddresses
.append( email
);
716 if ( signature
.creationTime() )
717 messagePart
.creationTime
.setTime_t( signature
.creationTime() );
719 messagePart
.creationTime
= QDateTime();
720 if ( messagePart
.signer
.isEmpty() ) {
721 if ( key
.numUserIDs() > 0 && key
.userID( 0 ).name() )
722 messagePart
.signer
= Kleo::DN( key
.userID( 0 ).name() ).prettyDN();
723 if ( !messagePart
.signerMailAddresses
.empty() ) {
724 if ( messagePart
.signer
.isEmpty() )
725 messagePart
.signer
= messagePart
.signerMailAddresses
.front();
727 messagePart
.signer
+= " <" + messagePart
.signerMailAddresses
.front() + '>';
730 #ifdef DEBUG_SIGNATURE
731 kDebug() << "\n key id:" << messagePart
.keyId
732 << "\n key trust:" << messagePart
.keyTrust
733 << "\n signer:" << messagePart
.signer
;
736 messagePart
.creationTime
= QDateTime();
739 if ( !doCheck
|| !data
){
740 if ( cleartextData
|| !cleartext
.isEmpty() ) {
742 htmlWriter()->queue( writeSigstatHeader( messagePart
,
745 bIsOpaqueSigned
= true;
747 CryptoProtocolSaver
cpws( this, cryptProto
);
748 createAndParseTempNode( data
, doCheck
? cleartext
.data() : cleartextData
->data(),
749 "opaque signed data" );
752 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
755 else if ( !hideErrors
) {
758 txt
.append( i18n( "The crypto engine returned no cleartext data." ) );
759 txt
.append( "</h2></b>" );
760 txt
.append( "<br/> <br/>" );
761 txt
.append( i18n( "Status: " ) );
762 if ( !messagePart
.status
.isEmpty() ) {
764 txt
.append( messagePart
.status
);
765 txt
.append( "</i>" );
768 txt
.append( i18nc("Status of message unknown.","(unknown)") );
770 htmlWriter()->queue(txt
);
774 if ( htmlWriter() ) {
777 switch ( cryptPlugError
) {
778 case NOT_INITIALIZED
:
779 errorMsg
= i18n( "Crypto plug-in \"%1\" is not initialized.",
782 case CANT_VERIFY_SIGNATURES
:
783 errorMsg
= i18n( "Crypto plug-in \"%1\" cannot verify signatures.",
787 if ( cryptPlugDisplayName
.isEmpty() )
788 errorMsg
= i18n( "No appropriate crypto plug-in was found." );
790 errorMsg
= i18nc( "%1 is either 'OpenPGP' or 'S/MIME'",
791 "No %1 plug-in was found.",
792 cryptPlugDisplayName
);
795 messagePart
.errorText
= i18n( "The message is signed, but the "
796 "validity of the signature cannot be "
802 htmlWriter()->queue( writeSigstatHeader( messagePart
,
807 ObjectTreeParser
otp( this, true );
808 otp
.setAllowAsync( allowAsync() );
809 otp
.parseObjectTreeInternal( data
);
810 mRawReplyString
+= otp
.rawReplyString();
811 mTextualContent
+= otp
.textualContent();
812 if ( !otp
.textualContentCharset().isEmpty() )
813 mTextualContentCharset
= otp
.textualContentCharset();
816 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
818 #ifdef DEBUG_SIGNATURE
819 kDebug() << "done, returning" << ( bIsOpaqueSigned
? "TRUE" : "FALSE" );
821 //kDebug() << "DECRYPTED" << data;
822 return bIsOpaqueSigned
;
825 void ObjectTreeParser::writeDeferredDecryptionBlock()
827 const QString iconName
= "file:///" + KIconLoader::global()->iconPath( "document-decrypt",
828 KIconLoader::Small
);
829 const QString decryptedData
= "<div style=\"font-size:large; text-align:center;"
830 "padding-top:20pt;\">"
831 + i18n("This message is encrypted.")
833 "<div style=\"text-align:center; padding-bottom:20pt;\">"
834 "<a href=\"kmail:decryptMessage\">"
835 "<img src=\"" + iconName
.toUtf8() + "\"/>"
836 + i18n("Decrypt Message")
838 PartMetaData messagePart
;
839 messagePart
.isDecryptable
= true;
840 messagePart
.isEncrypted
= true;
841 messagePart
.isSigned
= false;
842 mRawReplyString
+= decryptedData
.toUtf8();
844 if ( htmlWriter() ) { //TODO: check if this check should be here or at the beginning of the method
845 htmlWriter()->queue( writeSigstatHeader( messagePart
,
848 htmlWriter()->queue( decryptedData
);
849 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
854 void ObjectTreeParser::writeDecryptionInProgressBlock()
858 // PENDING(marc) find an animated icon here:
859 //const QString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small );
860 const QString decryptedData
= i18n("Encrypted data not shown");
861 PartMetaData messagePart
;
862 messagePart
.isDecryptable
= true;
863 messagePart
.isEncrypted
= true;
864 messagePart
.isSigned
= false;
865 messagePart
.inProgress
= true;
866 htmlWriter()->queue( writeSigstatHeader( messagePart
,
869 //htmlWriter()->queue( decryptedData );
870 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
873 void ObjectTreeParser::writeCertificateImportResult( const GpgME::ImportResult
& res
)
876 htmlWriter()->queue( i18n( "Sorry, certificate could not be imported.<br />"
877 "Reason: %1", QString::fromLocal8Bit( res
.error().asString() ) ) );
881 const int nImp
= res
.numImported();
882 const int nUnc
= res
.numUnchanged();
883 const int nSKImp
= res
.numSecretKeysImported();
884 const int nSKUnc
= res
.numSecretKeysUnchanged();
885 if ( !nImp
&& !nSKImp
&& !nUnc
&& !nSKUnc
) {
886 htmlWriter()->queue( i18n( "Sorry, no certificates were found in this message." ) );
889 QString comment
= "<b>" + i18n( "Certificate import status:" ) + "</b><br/> <br/>";
891 comment
+= i18np( "1 new certificate was imported.",
892 "%1 new certificates were imported.", nImp
) + "<br/>";
894 comment
+= i18np( "1 certificate was unchanged.",
895 "%1 certificates were unchanged.", nUnc
) + "<br/>";
897 comment
+= i18np( "1 new secret key was imported.",
898 "%1 new secret keys were imported.", nSKImp
) + "<br/>";
900 comment
+= i18np( "1 secret key was unchanged.",
901 "%1 secret keys were unchanged.", nSKUnc
) + "<br/>";
902 comment
+= " <br/>";
903 htmlWriter()->queue( comment
);
904 if ( !nImp
&& !nSKImp
) {
905 htmlWriter()->queue( "<hr>" );
908 const std::vector
<GpgME::Import
> imports
= res
.imports();
909 if ( imports
.empty() ) {
910 htmlWriter()->queue( i18n( "Sorry, no details on certificate import available." ) + "<hr>" );
913 htmlWriter()->queue( "<b>" + i18n( "Certificate import details:" ) + "</b><br/>" );
914 for ( std::vector
<GpgME::Import
>::const_iterator it
= imports
.begin() ; it
!= imports
.end() ; ++it
) {
915 if ( (*it
).error() ) {
916 htmlWriter()->queue( i18nc( "Certificate import failed.", "Failed: %1 (%2)", (*it
).fingerprint(),
917 QString::fromLocal8Bit( (*it
).error().asString() ) ) );
918 } else if ( (*it
).status() & ~GpgME::Import::ContainedSecretKey
) {
919 if ( (*it
).status() & GpgME::Import::ContainedSecretKey
) {
920 htmlWriter()->queue( i18n( "New or changed: %1 (secret key available)", (*it
).fingerprint() ) );
922 htmlWriter()->queue( i18n( "New or changed: %1", (*it
).fingerprint() ) );
925 htmlWriter()->queue( "<br/>" );
928 htmlWriter()->queue( "<hr>" );
932 bool ObjectTreeParser::okDecryptMIME( KMime::Content
& data
,
933 QByteArray
& decryptedData
,
934 bool& signatureFound
,
935 std::vector
<GpgME::Signature
> &signatures
,
937 bool& passphraseError
,
938 bool& actuallyEncrypted
,
939 bool& decryptionStarted
,
940 PartMetaData
&partMetaData
)
942 passphraseError
= false;
943 decryptionStarted
= false;
944 partMetaData
.errorText
.clear();
945 partMetaData
.auditLogError
= GpgME::Error();
946 partMetaData
.auditLog
.clear();
947 bool bDecryptionOk
= false;
948 enum { NO_PLUGIN
, NOT_INITIALIZED
, CANT_DECRYPT
}
949 cryptPlugError
= NO_PLUGIN
;
951 const Kleo::CryptoBackend::Protocol
* cryptProto
= cryptoProtocol();
953 QString cryptPlugLibName
;
955 cryptPlugLibName
= cryptProto
->name();
957 assert( mSource
->decryptMessage() );
959 const QString errorMsg
= i18n( "Could not decrypt the data." );
960 if ( cryptProto
/*FIXME(Andras) port to akonadi
961 && !kmkernel->contextMenuShown()*/ ) {
962 QByteArray ciphertext
= data
.decodedContent();
964 QString cipherStr
= QString::fromLatin1( ciphertext
);
965 bool cipherIsBinary
= ( !cipherStr
.contains("BEGIN ENCRYPTED MESSAGE", Qt::CaseInsensitive
) ) &&
966 ( !cipherStr
.contains("BEGIN PGP ENCRYPTED MESSAGE", Qt::CaseInsensitive
) ) &&
967 ( !cipherStr
.contains("BEGIN PGP MESSAGE", Qt::CaseInsensitive
) );
969 dumpToFile( "dat_04_reader.encrypted", ciphertext
.data(), ciphertext
.size() );
972 deb
= "\n\nE N C R Y P T E D D A T A = ";
973 if ( cipherIsBinary
)
974 deb
+= "[binary data]";
985 //kDebug() << "going to call CRYPTPLUG" << cryptPlugLibName;
987 // Check whether the memento contains a result from last time:
988 const DecryptVerifyBodyPartMemento
* m
989 = dynamic_cast<DecryptVerifyBodyPartMemento
*>( mNodeHelper
->bodyPartMemento( &data
, "decryptverify" ) );
991 Kleo::DecryptVerifyJob
* job
= cryptProto
->decryptVerifyJob();
993 cryptPlugError
= CANT_DECRYPT
;
996 DecryptVerifyBodyPartMemento
* newM
997 = new DecryptVerifyBodyPartMemento( job
, ciphertext
);
998 if ( allowAsync() ) {
999 QObject::connect( newM
, SIGNAL(update(MessageViewer::Viewer::UpdateMode
)), mSource
->sourceObject(),
1000 SLOT(update(MessageViewer::Viewer::UpdateMode
)) );
1001 if ( newM
->start() ) {
1002 decryptionStarted
= true;
1003 mHasPendingAsyncJobs
= true;
1011 mNodeHelper
->setBodyPartMemento( &data
, "decryptverify", newM
);
1013 } else if ( m
->isRunning() ) {
1014 decryptionStarted
= true;
1015 mHasPendingAsyncJobs
= true;
1020 const QByteArray
& plainText
= m
->plainText();
1021 const GpgME::DecryptionResult
& decryptResult
= m
->decryptResult();
1022 const GpgME::VerificationResult
& verifyResult
= m
->verifyResult();
1023 std::stringstream ss
;
1024 ss
<< decryptResult
<< '\n' << verifyResult
;
1025 //kDebug() << ss.str().c_str();
1026 signatureFound
= verifyResult
.signatures().size() > 0;
1027 signatures
= verifyResult
.signatures();
1028 bDecryptionOk
= !decryptResult
.error();
1029 passphraseError
= decryptResult
.error().isCanceled()
1030 || decryptResult
.error().code() == GPG_ERR_NO_SECKEY
;
1031 actuallyEncrypted
= decryptResult
.error().code() != GPG_ERR_NO_DATA
;
1032 partMetaData
.errorText
= QString::fromLocal8Bit( decryptResult
.error().asString() );
1033 partMetaData
.auditLogError
= m
->auditLogError();
1034 partMetaData
.auditLog
= m
->auditLogAsHtml();
1035 partMetaData
.isEncrypted
= actuallyEncrypted
;
1036 if ( actuallyEncrypted
&& decryptResult
.numRecipients() > 0 )
1037 partMetaData
.keyId
= decryptResult
.recipient( 0 ).keyID();
1039 //kDebug() << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG";
1040 if ( bDecryptionOk
)
1041 decryptedData
= plainText
;
1042 else if ( htmlWriter() && showWarning
) {
1043 decryptedData
= "<div style=\"font-size:x-large; text-align:center;"
1047 if ( !passphraseError
)
1048 partMetaData
.errorText
= i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName
)
1050 + i18n("Error: %1", partMetaData
.errorText
);
1055 if ( !cryptProto
) {
1056 decryptedData
= "<div style=\"text-align:center; padding:20pt;\">"
1059 switch ( cryptPlugError
) {
1060 case NOT_INITIALIZED
:
1061 partMetaData
.errorText
= i18n( "Crypto plug-in \"%1\" is not initialized.",
1065 partMetaData
.errorText
= i18n( "Crypto plug-in \"%1\" cannot decrypt messages.",
1069 partMetaData
.errorText
= i18n( "No appropriate crypto plug-in was found." );
1072 } else if (/*FIXME(Andras) port to akonadi
1073 kmkernel->contextMenuShown()*/ false ) {
1074 // ### Workaround for bug 56693 (kmail freeze with the complete desktop
1075 // ### while pinentry-qt appears)
1076 QByteArray
ciphertext( data
.decodedContent() );
1077 QString cipherStr
= QString::fromLatin1( ciphertext
);
1078 bool cipherIsBinary
= ( !cipherStr
.contains("BEGIN ENCRYPTED MESSAGE", Qt::CaseInsensitive
) ) &&
1079 ( !cipherStr
.contains("BEGIN PGP ENCRYPTED MESSAGE", Qt::CaseInsensitive
) ) &&
1080 ( !cipherStr
.contains("BEGIN PGP MESSAGE", Qt::CaseInsensitive
) );
1081 if ( !cipherIsBinary
) {
1082 decryptedData
= ciphertext
;
1085 decryptedData
= "<div style=\"font-size:x-large; text-align:center;"
1092 dumpToFile( "dat_05_reader.decrypted", decryptedData
.data(), decryptedData
.size() );
1094 return bDecryptionOk
;
1098 bool ObjectTreeParser::containsExternalReferences( const QString
& str
)
1100 int httpPos
= str
.indexOf( "\"http:", Qt::CaseInsensitive
);
1101 int httpsPos
= str
.indexOf( "\"https:", Qt::CaseInsensitive
);
1103 while ( httpPos
>= 0 || httpsPos
>= 0 ) {
1104 // pos = index of next occurrence of "http: or "https: whichever comes first
1105 int pos
= ( httpPos
< httpsPos
)
1106 ? ( ( httpPos
>= 0 ) ? httpPos
: httpsPos
)
1107 : ( ( httpsPos
>= 0 ) ? httpsPos
: httpPos
);
1108 // look backwards for "href"
1110 int hrefPos
= str
.lastIndexOf( "href", pos
- 5, Qt::CaseInsensitive
);
1111 // if no 'href' is found or the distance between 'href' and '"http[s]:'
1112 // is larger than 7 (7 is the distance in 'href = "http[s]:') then
1113 // we assume that we have found an external reference
1114 if ( ( hrefPos
== -1 ) || ( pos
- hrefPos
> 7 ) ) {
1116 // HTML messages created by KMail itself for now contain the following:
1117 // <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
1118 // Make sure not to show an external references warning for this string
1119 int dtdPos
= str
.indexOf( "http://www.w3.org/TR/html4/loose.dtd", pos
+ 1 );
1120 if ( dtdPos
!= ( pos
+ 1 ) )
1124 // find next occurrence of "http: or "https:
1125 if ( pos
== httpPos
) {
1126 httpPos
= str
.indexOf( "\"http:", httpPos
+ 6, Qt::CaseInsensitive
);
1129 httpsPos
= str
.indexOf( "\"https:", httpsPos
+ 7, Qt::CaseInsensitive
);
1135 bool ObjectTreeParser::processTextHtmlSubtype( KMime::Content
* curNode
, ProcessResult
& ) {
1136 const QByteArray
partBody( curNode
->decodedContent() );
1138 mRawReplyString
= partBody
;
1139 if ( curNode
->topLevel()->textContent() == curNode
) {
1140 mTextualContent
+= curNode
->decodedText();
1141 mTextualContentCharset
= NodeHelper::charset( curNode
);
1144 if ( !htmlWriter() )
1148 if ( mSource
->htmlMail() )
1149 bodyText
= codecFor( curNode
)->toUnicode( partBody
);
1151 bodyText
= StringUtil::convertAngleBracketsToHtml( partBody
);
1153 if ( curNode
->topLevel()->textContent() == curNode
|| attachmentStrategy()->defaultDisplay( curNode
) == AttachmentStrategy::Inline
||
1154 showOnlyOneMimePart() )
1156 if ( mSource
->htmlMail() ) {
1158 HTMLQuoteColorer colorer
;
1159 for ( int i
= 0; i
< 2; i
++ )
1160 colorer
.setQuoteColor( i
, cssHelper()->quoteColor( i
) );
1161 bodyText
= colorer
.process( bodyText
);
1162 mNodeHelper
->setNodeDisplayedEmbedded( curNode
, true );
1164 // Show the "external references" warning (with possibility to load
1165 // external references only if loading external references is disabled
1166 // and the HTML code contains obvious external references). For
1167 // messages where the external references are obfuscated the user won't
1168 // have an easy way to load them but that shouldn't be a problem
1169 // because only spam contains obfuscated external references.
1170 if ( !mSource
->htmlLoadExternal() &&
1171 containsExternalReferences( bodyText
) ) {
1172 htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
1173 htmlWriter()->queue( i18n("<b>Note:</b> This HTML message may contain external "
1174 "references to images etc. For security/privacy reasons "
1175 "external references are not loaded. If you trust the "
1176 "sender of this message then you can load the external "
1177 "references for this message "
1178 "<a href=\"kmail:loadExternal\">by clicking here</a>.") );
1179 htmlWriter()->queue( "</div><br/><br/>" );
1182 htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
1183 htmlWriter()->queue( i18n("<b>Note:</b> This is an HTML message. For "
1184 "security reasons, only the raw HTML code "
1185 "is shown. If you trust the sender of this "
1186 "message then you can activate formatted "
1187 "HTML display for this message "
1188 "<a href=\"kmail:showHTML\">by clicking here</a>.") );
1189 htmlWriter()->queue( "</div><br/><br/>" );
1191 // Make sure the body is relative, so that nothing is painted over above "Note: ..."
1192 // if a malicious message uses absolute positioning. #137643
1193 htmlWriter()->queue( "<div style=\"position: relative\">\n" );
1194 htmlWriter()->queue( bodyText
);
1195 htmlWriter()->queue( "</div>\n" );
1196 mSource
->setHtmlMode( Util::Html
);
1202 bool ObjectTreeParser::isMailmanMessage( KMime::Content
* curNode
)
1204 if ( !curNode
|| curNode
->head().isEmpty() )
1206 if ( curNode
->hasHeader("X-Mailman-Version") )
1208 if ( curNode
->hasHeader("X-Mailer") ) {
1209 KMime::Headers::Base
*header
= curNode
->headerByType("X-Mailer");
1210 if ( header
->asUnicodeString().contains("MAILMAN", Qt::CaseInsensitive
) )
1216 bool ObjectTreeParser::processMailmanMessage( KMime::Content
* curNode
) {
1217 const QString str
= QString::fromLatin1( curNode
->decodedContent() );
1220 const QLatin1String
delim1( "--__--__--\n\nMessage:" );
1221 const QLatin1String
delim2( "--__--__--\r\n\r\nMessage:" );
1222 const QLatin1String
delimZ2( "--__--__--\n\n_____________" );
1223 const QLatin1String
delimZ1( "--__--__--\r\n\r\n_____________" );
1224 QString partStr
, digestHeaderStr
;
1225 int thisDelim
= str
.indexOf( delim1
, Qt::CaseInsensitive
);
1226 if ( thisDelim
== -1 ) {
1227 thisDelim
= str
.indexOf( delim2
, Qt::CaseInsensitive
);
1229 if ( thisDelim
== -1 ) {
1233 int nextDelim
= str
.indexOf( delim1
, thisDelim
+1, Qt::CaseInsensitive
);
1234 if ( -1 == nextDelim
) {
1235 nextDelim
= str
.indexOf( delim2
, thisDelim
+1, Qt::CaseInsensitive
);
1237 if ( -1 == nextDelim
) {
1238 nextDelim
= str
.indexOf( delimZ1
, thisDelim
+1, Qt::CaseInsensitive
);
1240 if ( -1 == nextDelim
) {
1241 nextDelim
= str
.indexOf( delimZ2
, thisDelim
+1, Qt::CaseInsensitive
);
1243 if ( nextDelim
< 0) {
1247 //if ( curNode->mRoot )
1248 // curNode = curNode->mRoot;
1250 // at least one message found: build a mime tree
1251 digestHeaderStr
= "Content-Type: text/plain\nContent-Description: digest header\n\n";
1252 digestHeaderStr
+= str
.mid( 0, thisDelim
);
1253 createAndParseTempNode( mTopLevelContent
, digestHeaderStr
.toLatin1(), "Digest Header" );
1254 //mReader->queueHtml("<br><hr><br>");
1255 // temporarily change curent node's Content-Type
1256 // to get our embedded RfC822 messages properly inserted
1257 curNode
->contentType()->setMimeType( "multipart/digest" );
1258 while( -1 < nextDelim
){
1259 int thisEoL
= str
.indexOf("\nMessage:", thisDelim
, Qt::CaseInsensitive
);
1261 thisDelim
= thisEoL
+1;
1263 thisEoL
= str
.indexOf("\n_____________", thisDelim
, Qt::CaseInsensitive
);
1265 thisDelim
= thisEoL
+1;
1267 thisEoL
= str
.indexOf( '\n', thisDelim
);
1269 thisDelim
= thisEoL
+1;
1271 thisDelim
= thisDelim
+1;
1272 //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] )
1275 partStr
= "Content-Type: message/rfc822\nContent-Description: embedded message\n\n";
1276 partStr
+= "Content-Type: text/plain\n";
1277 partStr
+= str
.mid( thisDelim
, nextDelim
-thisDelim
);
1278 QString subject
= QString::fromLatin1("embedded message");
1279 QString subSearch
= QString::fromLatin1("\nSubject:");
1280 int subPos
= partStr
.indexOf(subSearch
, 0, Qt::CaseInsensitive
);
1282 subject
= partStr
.mid(subPos
+subSearch
.length());
1283 thisEoL
= subject
.indexOf('\n');
1285 subject
.truncate( thisEoL
);
1287 kDebug() << " embedded message found: \"" << subject
;
1288 createAndParseTempNode( mTopLevelContent
, partStr
.toLatin1(), subject
.toLatin1() );
1289 //mReader->queueHtml("<br><hr><br>");
1290 thisDelim
= nextDelim
+1;
1291 nextDelim
= str
.indexOf(delim1
, thisDelim
, Qt::CaseInsensitive
);
1292 if ( -1 == nextDelim
)
1293 nextDelim
= str
.indexOf(delim2
, thisDelim
, Qt::CaseInsensitive
);
1294 if ( -1 == nextDelim
)
1295 nextDelim
= str
.indexOf(delimZ1
, thisDelim
, Qt::CaseInsensitive
);
1296 if ( -1 == nextDelim
)
1297 nextDelim
= str
.indexOf(delimZ2
, thisDelim
, Qt::CaseInsensitive
);
1299 // reset curent node's Content-Type
1300 curNode
->contentType()->setMimeType( "text/plain" );
1301 int thisEoL
= str
.indexOf( "_____________", thisDelim
);
1302 if ( -1 < thisEoL
){
1303 thisDelim
= thisEoL
;
1304 thisEoL
= str
.indexOf( '\n', thisDelim
);
1306 thisDelim
= thisEoL
+1;
1309 thisDelim
= thisDelim
+1;
1310 partStr
= "Content-Type: text/plain\nContent-Description: digest footer\n\n";
1311 partStr
+= str
.mid( thisDelim
);
1312 createAndParseTempNode( mTopLevelContent
, partStr
.toLatin1(), "Digest Footer" );
1316 bool ObjectTreeParser::processTextPlainSubtype( KMime::Content
*curNode
, ProcessResult
& result
)
1318 const bool isFirstTextPart
= ( curNode
->topLevel()->textContent() == curNode
);
1320 if ( !htmlWriter() ) {
1321 mRawReplyString
= curNode
->decodedContent();
1322 if ( isFirstTextPart
) {
1323 mTextualContent
+= curNode
->decodedText();
1324 mTextualContentCharset
+= NodeHelper::charset( curNode
);
1329 if ( !isFirstTextPart
&& attachmentStrategy()->defaultDisplay( curNode
) != AttachmentStrategy::Inline
&&
1330 !showOnlyOneMimePart() )
1333 // TODO: Remove code duplication
1334 mRawReplyString
= curNode
->decodedContent();
1335 if ( isFirstTextPart
) {
1336 mTextualContent
+= curNode
->decodedText();
1337 mTextualContentCharset
= NodeHelper::charset( curNode
);
1340 QString label
= NodeHelper::fileName( curNode
);
1342 const bool bDrawFrame
= !isFirstTextPart
1343 && !showOnlyOneMimePart()
1344 && !label
.isEmpty();
1346 label
= StringUtil::quoteHtmlChars( label
, true );
1348 const QString comment
=
1349 StringUtil::quoteHtmlChars( curNode
->contentDescription()->asUnicodeString(), true );
1351 const QString fileName
;
1352 mNodeHelper
->writeNodeToTempFile( curNode
);
1353 const QString dir
= QApplication::isRightToLeft() ? "rtl" : "ltr" ;
1355 QString htmlStr
= "<table cellspacing=\"1\" class=\"textAtm\">"
1356 "<tr class=\"textAtmH\"><td dir=\"" + dir
+ "\">";
1357 if ( !fileName
.isEmpty() )
1358 htmlStr
+= "<a href=\"" + mNodeHelper
->asHREF( curNode
, "body" ) + "\">"
1362 if ( !comment
.isEmpty() )
1363 htmlStr
+= "<br/>" + comment
;
1364 htmlStr
+= "</td></tr><tr class=\"textAtmB\"><td>";
1366 htmlWriter()->queue( htmlStr
);
1368 // process old style not-multipart Mailman messages to
1369 // enable verification of the embedded messages' signatures
1370 if ( !isMailmanMessage( curNode
) ||
1371 !processMailmanMessage( curNode
) ) {
1372 writeBodyString( mRawReplyString
, NodeHelper::fromAsString( curNode
),
1373 codecFor( curNode
), result
, !bDrawFrame
);
1374 mNodeHelper
->setNodeDisplayedEmbedded( curNode
, true );
1377 htmlWriter()->queue( "</td></tr></table>" );
1382 void ObjectTreeParser::stdChildHandling( KMime::Content
* child
) {
1386 ObjectTreeParser
otp( *this );
1387 otp
.setShowOnlyOneMimePart( false );
1388 otp
.parseObjectTreeInternal( child
);
1389 mRawReplyString
+= otp
.rawReplyString();
1390 mTextualContent
+= otp
.textualContent();
1391 if ( !otp
.textualContentCharset().isEmpty() )
1392 mTextualContentCharset
= otp
.textualContentCharset();
1395 QString
ObjectTreeParser::defaultToltecReplacementText()
1397 return i18n( "This message is a <i>Toltec</i> Groupware object, it can only be viewed with "
1398 "Microsoft Outlook in combination with the Toltec connector." );
1401 bool ObjectTreeParser::processToltecMail( KMime::Content
*node
)
1403 if ( !node
|| !htmlWriter() || !GlobalSettings::self()->showToltecReplacementText() ||
1404 !NodeHelper::isToltecMessage( node
) || mShowRawToltecMail
)
1407 htmlWriter()->queue( GlobalSettings::self()->toltecReplacementText() );
1408 htmlWriter()->queue( "<br/><br/><a href=\"kmail:showRawToltecMail\">" +
1409 i18n( "Show Raw Message" ) + "</a>" );
1413 bool ObjectTreeParser::processMultiPartMixedSubtype( KMime::Content
* node
, ProcessResult
& )
1415 if ( processToltecMail( node
) ) {
1419 KMime::Content
* child
= MessageCore::NodeHelper::firstChild( node
);
1423 // normal treatment of the parts in the mp/mixed container
1424 stdChildHandling( child
);
1428 bool ObjectTreeParser::processMultiPartAlternativeSubtype( KMime::Content
* node
, ProcessResult
& )
1430 KMime::Content
* child
= MessageCore::NodeHelper::firstChild( node
);
1434 KMime::Content
* dataHtml
= findType( child
, "text/html", false, true );
1436 // If we didn't find the HTML part as the first child of the multipart/alternative, it might
1437 // be that this is a HTML message with images, and text/plain and multipart/related are the
1438 // immediate children of this multipart/alternative node.
1439 // In this case, the HTML node is a child of multipart/related.
1440 dataHtml
= findType( child
, "multipart/related", false, true );
1442 // Still not found? Stupid apple mail actually puts the attachments inside of the
1443 // multipart/alternative, which is wrong. Therefore we also have to look for multipart/mixed
1445 // Do this only when prefering HTML mail, though, since otherwise the attachments are hidden
1446 // when displaying plain text.
1447 if ( !dataHtml
&& mSource
->htmlMail() ) {
1448 dataHtml
= findType( child
, "multipart/mixed", false, true );
1452 KMime::Content
* dataPlain
= findType( child
, "text/plain", false, true );
1454 if ( ( mSource
->htmlMail() && dataHtml
) ||
1455 (dataHtml
&& dataPlain
&& dataPlain
->body().isEmpty()) ) {
1457 mNodeHelper
->setNodeProcessed( dataPlain
, false);
1458 stdChildHandling( dataHtml
);
1459 mSource
->setHtmlMode( Util::MultipartHtml
);
1463 if ( !htmlWriter() || (!mSource
->htmlMail() && dataPlain
) ) {
1464 mNodeHelper
->setNodeProcessed( dataHtml
, false );
1465 stdChildHandling( dataPlain
);
1466 mSource
->setHtmlMode( Util::MultipartPlain
);
1470 stdChildHandling( child
);
1474 bool ObjectTreeParser::processMultiPartDigestSubtype( KMime::Content
* node
, ProcessResult
& result
) {
1475 return processMultiPartMixedSubtype( node
, result
);
1478 bool ObjectTreeParser::processMultiPartParallelSubtype( KMime::Content
* node
, ProcessResult
& result
) {
1479 return processMultiPartMixedSubtype( node
, result
);
1482 bool ObjectTreeParser::processMultiPartSignedSubtype( KMime::Content
* node
, ProcessResult
& )
1484 KMime::Content
* child
= MessageCore::NodeHelper::firstChild( node
);
1485 if ( node
->contents().size() != 2 ) {
1486 kDebug() << "mulitpart/signed must have exactly two child parts!" << endl
1487 << "processing as multipart/mixed";
1489 stdChildHandling( child
);
1493 KMime::Content
* signedData
= child
;
1494 assert( signedData
);
1496 KMime::Content
* signature
= node
->contents().at(1);
1497 assert( signature
);
1499 mNodeHelper
->setNodeProcessed( signature
, true);
1501 if ( !includeSignatures() ) {
1502 stdChildHandling( signedData
);
1506 QString protocolContentType
= node
->contentType()->parameter( "protocol" ).toLower();
1507 const QString signatureContentType
= signature
->contentType()->mimeType().toLower();
1508 if ( protocolContentType
.isEmpty() ) {
1509 kWarning() << "Message doesn't set the protocol for the multipart/signed content-type, "
1510 "using content-type of the signature:" << signatureContentType
;
1511 protocolContentType
= signatureContentType
;
1514 const Kleo::CryptoBackend::Protocol
*protocol
= 0;
1515 if ( protocolContentType
== "application/pkcs7-signature" ||
1516 protocolContentType
== "application/x-pkcs7-signature" )
1517 protocol
= Kleo::CryptoBackendFactory::instance()->smime();
1518 else if ( protocolContentType
== "application/pgp-signature" ||
1519 protocolContentType
== "application/x-pgp-signature" )
1520 protocol
= Kleo::CryptoBackendFactory::instance()->openpgp();
1523 mNodeHelper
->setNodeProcessed( signature
, true );
1524 stdChildHandling( signedData
);
1528 CryptoProtocolSaver
saver( this, protocol
);
1529 mNodeHelper
->setSignatureState( node
, KMMsgFullySigned
);
1531 writeOpaqueOrMultipartSignedData( signedData
, *signature
,
1532 NodeHelper::fromAsString( node
) );
1536 bool ObjectTreeParser::processMultiPartEncryptedSubtype( KMime::Content
* node
, ProcessResult
& result
)
1538 KMime::Content
* child
= MessageCore::NodeHelper::firstChild( node
);
1542 if ( keepEncryptions() ) {
1543 mNodeHelper
->setEncryptionState( node
, KMMsgFullyEncrypted
);
1544 const QByteArray cstr
= node
->decodedContent();
1545 if ( htmlWriter() ) {
1546 writeBodyString( cstr
, NodeHelper::fromAsString( node
),
1547 codecFor( node
), result
, false );
1549 mRawReplyString
+= cstr
;
1553 const Kleo::CryptoBackend::Protocol
* useThisCryptProto
= 0;
1556 ATTENTION: This code is to be replaced by the new 'auto-detect' feature. --------------------------------------
1558 KMime::Content
* data
= findType( child
, "application/octet-stream", false, true );
1560 useThisCryptProto
= Kleo::CryptoBackendFactory::instance()->openpgp();
1563 data
= findType( child
, "application/pkcs7-mime", false, true );
1565 useThisCryptProto
= Kleo::CryptoBackendFactory::instance()->smime();
1569 ---------------------------------------------------------------------------------------------------------------
1573 stdChildHandling( child
);
1577 CryptoProtocolSaver
cpws( this, useThisCryptProto
);
1579 KMime::Content
* dataChild
= MessageCore::NodeHelper::firstChild( data
);
1581 stdChildHandling( dataChild
);
1585 mNodeHelper
->setEncryptionState( node
, KMMsgFullyEncrypted
);
1587 if ( !mSource
->decryptMessage() ) {
1588 writeDeferredDecryptionBlock();
1589 mNodeHelper
->setNodeProcessed( data
, false );// Set the data node to done to prevent it from being processed
1593 PartMetaData messagePart
;
1594 // if we already have a decrypted node for this encrypted node, don't do the decryption again
1595 if ( KMime::Content
* newNode
= mNodeHelper
->decryptedNodeForContent( data
) )
1597 // if( NodeHelper::nodeProcessed( data ) )
1598 ObjectTreeParser
otp( this );
1599 otp
.parseObjectTreeInternal( newNode
);
1600 mRawReplyString
+= otp
.rawReplyString();
1601 mTextualContent
+= otp
.textualContent();
1602 if ( !otp
.textualContentCharset().isEmpty() )
1603 mTextualContentCharset
= otp
.textualContentCharset();
1605 messagePart
= mNodeHelper
->partMetaData( node
);
1607 QByteArray decryptedData
;
1608 bool signatureFound
;
1609 std::vector
<GpgME::Signature
> signatures
;
1610 bool passphraseError
;
1611 bool actuallyEncrypted
= true;
1612 bool decryptionStarted
;
1614 bool bOkDecrypt
= okDecryptMIME( *data
,
1623 //kDebug() << "decrypted, signed?:" << signatureFound;
1625 if ( decryptionStarted
) {
1626 writeDecryptionInProgressBlock();
1630 mNodeHelper
->setNodeProcessed( data
, false ); // Set the data node to done to prevent it from being processed
1633 if ( htmlWriter() ) {
1634 messagePart
.isDecryptable
= bOkDecrypt
;
1635 messagePart
.isEncrypted
= true;
1636 messagePart
.isSigned
= false;
1637 htmlWriter()->queue( writeSigstatHeader( messagePart
,
1639 NodeHelper::fromAsString( node
) ) );
1643 // Note: Multipart/Encrypted might also be signed
1644 // without encapsulating a nicely formatted
1645 // ~~~~~~~ Multipart/Signed part.
1646 // (see RFC 3156 --> 6.2)
1647 // In this case we paint a _2nd_ frame inside the
1648 // encryption frame, but we do _not_ show a respective
1649 // encapsulated MIME part in the Mime Tree Viewer
1650 // since we do want to show the _true_ structure of the
1651 // message there - not the structure that the sender's
1652 // MUA 'should' have sent. :-D (khz, 12.09.2002)
1654 if ( signatureFound
) {
1655 writeOpaqueOrMultipartSignedData( 0,
1657 NodeHelper::fromAsString( node
),
1662 mNodeHelper
->setSignatureState( node
, KMMsgFullySigned
);
1663 //kDebug() << "setting FULLY SIGNED to:" << node;
1665 decryptedData
= KMime::CRLFtoLF( decryptedData
); //KMime works with LF only inside insertAndParseNewChildNode
1667 createAndParseTempNode( node
, decryptedData
.constData(),"encrypted data" );
1670 mRawReplyString
+= decryptedData
;
1671 if ( htmlWriter() ) {
1672 // print the error message that was returned in decryptedData
1674 htmlWriter()->queue( QString::fromUtf8( decryptedData
.data() ) );
1679 mNodeHelper
->setPartMetaData( node
, messagePart
);
1683 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
1688 bool ObjectTreeParser::processMessageRfc822Subtype( KMime::Content
* node
, ProcessResult
& )
1690 if ( htmlWriter() && !attachmentStrategy()->inlineNestedMessages() && !showOnlyOneMimePart() )
1693 PartMetaData messagePart
;
1694 messagePart
.isEncrypted
= false;
1695 messagePart
.isSigned
= false;
1696 messagePart
.isEncapsulatedRfc822Message
= true;
1698 KMime::Message::Ptr message
= node
->bodyAsMessage();
1700 kWarning() << "Node is of type message/rfc822 but doesn't have a message!";
1703 if ( htmlWriter() && message
) {
1705 // The link to "Encapsulated message" is clickable, therefore the temp file needs to exists,
1706 // since the user can click the link and expect to have normal attachment operations there.
1707 mNodeHelper
->writeNodeToTempFile( message
.get() );
1709 // Paint the frame header
1710 htmlWriter()->queue( writeSigstatHeader( messagePart
,
1712 message
->from()->asUnicodeString(),
1715 // Paint the message header
1716 htmlWriter()->queue( mSource
->createMessageHeader( message
.get() ) );
1718 // Process the message, i.e. paint it by processing it with an OTP
1719 ObjectTreeParser
otp( this );
1720 otp
.parseObjectTreeInternal( message
.get() );
1722 // Don't add the resulting textual content to our textual content here.
1723 // That is unwanted when inline forwarding a message, since the encapsulated message will
1724 // already be in the forward message as attachment, so don't duplicate the textual content
1725 // by adding it to the inline body as well
1727 // Paint the frame footer
1728 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
1731 mNodeHelper
->setNodeDisplayedEmbedded( node
, true );
1732 mNodeHelper
->setPartMetaData( node
, messagePart
);
1738 bool ObjectTreeParser::processApplicationOctetStreamSubtype( KMime::Content
* node
, ProcessResult
& result
)
1740 if ( KMime::Content
* child
= mNodeHelper
->decryptedNodeForContent( node
) ) {
1741 ObjectTreeParser
otp( this );
1742 otp
.parseObjectTreeInternal( child
);
1743 mRawReplyString
+= otp
.rawReplyString();
1744 mTextualContent
+= otp
.textualContent();
1745 if ( !otp
.textualContentCharset().isEmpty() )
1746 mTextualContentCharset
= otp
.textualContentCharset();
1750 const Kleo::CryptoBackend::Protocol
* oldUseThisCryptPlug
= cryptoProtocol();
1752 && node
->parent()->contentType()->mimeType() == "multipart/encrypted" ) {
1753 mNodeHelper
->setEncryptionState( node
, KMMsgFullyEncrypted
);
1754 if ( keepEncryptions() ) {
1755 const QByteArray cstr
= node
->decodedContent();
1756 if ( htmlWriter() ) {
1757 writeBodyString( cstr
, NodeHelper::fromAsString( node
),
1758 codecFor( node
), result
, false );
1760 mRawReplyString
+= cstr
;
1761 } else if ( !mSource
->decryptMessage() ) {
1762 writeDeferredDecryptionBlock();
1765 ATTENTION: This code is to be replaced by the planned 'auto-detect' feature.
1767 PartMetaData messagePart
;
1768 setCryptoProtocol( Kleo::CryptoBackendFactory::instance()->openpgp() );
1769 QByteArray decryptedData
;
1770 bool signatureFound
;
1771 std::vector
<GpgME::Signature
> signatures
;
1772 bool passphraseError
;
1773 bool actuallyEncrypted
= true;
1774 bool decryptionStarted
;
1776 bool bOkDecrypt
= okDecryptMIME( *node
,
1786 if ( decryptionStarted
) {
1787 writeDecryptionInProgressBlock();
1792 if ( htmlWriter() ) {
1793 messagePart
.isDecryptable
= bOkDecrypt
;
1794 messagePart
.isEncrypted
= true;
1795 messagePart
.isSigned
= false;
1796 htmlWriter()->queue( writeSigstatHeader( messagePart
,
1798 NodeHelper::fromAsString( node
) ) );
1802 // fixing the missing attachments bug #1090-b
1803 createAndParseTempNode( node
, decryptedData
.constData(), "encrypted data" );
1805 mRawReplyString
+= decryptedData
;
1806 if ( htmlWriter() ) {
1807 // print the error message that was returned in decryptedData
1809 htmlWriter()->queue( QString::fromUtf8( decryptedData
.data() ) );
1814 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
1815 mNodeHelper
->setPartMetaData( node
, messagePart
);
1819 setCryptoProtocol( oldUseThisCryptPlug
);
1823 bool ObjectTreeParser::processApplicationPkcs7MimeSubtype( KMime::Content
* node
, ProcessResult
& result
)
1825 if ( KMime::Content
* child
= mNodeHelper
->decryptedNodeForContent( node
) ) {
1826 ObjectTreeParser
otp( this );
1827 otp
.parseObjectTreeInternal( child
);
1828 mRawReplyString
+= otp
.rawReplyString();
1829 mTextualContent
+= otp
.textualContent();
1830 if ( !otp
.textualContentCharset().isEmpty() )
1831 mTextualContentCharset
= otp
.textualContentCharset();
1835 if ( node
->head().isEmpty() )
1838 const Kleo::CryptoBackend::Protocol
* smimeCrypto
= Kleo::CryptoBackendFactory::instance()->smime();
1842 const QString smimeType
= node
->contentType()->parameter("smime-type").toLower();
1844 if ( smimeType
== "certs-only" ) {
1845 result
.setNeverDisplayInline( true );
1846 if ( !htmlWriter() )
1849 if ( !GlobalSettings::self()->autoImportKeys() )
1852 const QByteArray certData
= node
->decodedContent();
1854 Kleo::ImportJob
*import
= smimeCrypto
->importJob();
1855 KleoJobExecutor executor
;
1856 const GpgME::ImportResult res
= executor
.exec( import
, certData
);
1857 writeCertificateImportResult( res
);
1861 CryptoProtocolSaver
cpws( this, smimeCrypto
);
1863 bool isSigned
= smimeType
== "signed-data";
1864 bool isEncrypted
= smimeType
== "enveloped-data";
1866 // Analyze "signTestNode" node to find/verify a signature.
1867 // If zero this verification was successfully done after
1868 // decrypting via recursion by insertAndParseNewChildNode().
1869 KMime::Content
* signTestNode
= isEncrypted
? 0 : node
;
1872 // We try decrypting the content
1873 // if we either *know* that it is an encrypted message part
1874 // or there is neither signed nor encrypted parameter.
1876 if ( isEncrypted
) {
1877 ;//kDebug() << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data";
1879 ;//kDebug() << "pkcs7 mime - type unknown - enveloped (encrypted) data ?";
1881 QByteArray decryptedData
;
1882 PartMetaData messagePart
;
1883 messagePart
.isEncrypted
= true;
1884 messagePart
.isSigned
= false;
1885 bool signatureFound
;
1886 std::vector
<GpgME::Signature
> signatures
;
1887 bool passphraseError
;
1888 bool actuallyEncrypted
= true;
1889 bool decryptionStarted
;
1891 if ( !mSource
->decryptMessage() ) {
1892 writeDeferredDecryptionBlock();
1894 signTestNode
= 0; // PENDING(marc) to be abs. sure, we'd need to have to look at the content
1896 const bool bOkDecrypt
= okDecryptMIME( *node
, decryptedData
, signatureFound
, signatures
,
1897 false, passphraseError
, actuallyEncrypted
,
1898 decryptionStarted
, messagePart
);
1899 //kDebug() << "PKCS7 found signature?" << signatureFound;
1900 if ( decryptionStarted
) {
1901 writeDecryptionInProgressBlock();
1906 //kDebug() << "pkcs7 mime - encryption found - enveloped (encrypted) data !";
1908 mNodeHelper
->setEncryptionState( node
, KMMsgFullyEncrypted
);
1909 if( signatureFound
)
1910 mNodeHelper
->setSignatureState( node
, KMMsgFullySigned
);
1913 messagePart
.isDecryptable
= true;
1915 htmlWriter()->queue( writeSigstatHeader( messagePart
,
1917 NodeHelper::fromAsString( node
) ) );
1918 createAndParseTempNode( node
, decryptedData
.constData(), "encrypted data" );
1920 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
1922 // decryption failed, which could be because the part was encrypted but
1923 // decryption failed, or because we didn't know if it was encrypted, tried,
1924 // and failed. If the message was not actually encrypted, we continue
1925 // assuming it's signed
1926 if ( passphraseError
|| ( smimeType
.isEmpty() && actuallyEncrypted
) ) {
1931 if ( isEncrypted
) {
1932 //kDebug() << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !";
1934 messagePart
.isDecryptable
= false;
1935 if ( htmlWriter() ) {
1936 htmlWriter()->queue( writeSigstatHeader( messagePart
,
1938 NodeHelper::fromAsString( node
) ) );
1939 assert( mSource
->decryptMessage() ); // handled above
1940 writePartIcon( node
);
1941 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
1944 //kDebug() << "pkcs7 mime - NO encryption found";
1949 mNodeHelper
->setEncryptionState( node
, KMMsgFullyEncrypted
);
1950 mNodeHelper
->setPartMetaData( node
, messagePart
);
1953 // We now try signature verification if necessarry.
1954 if ( signTestNode
) {
1956 ;//kDebug() << "pkcs7 mime == S/MIME TYPE: opaque signed data";
1958 ;//kDebug() << "pkcs7 mime - type unknown - opaque signed data ?";
1961 bool sigFound
= writeOpaqueOrMultipartSignedData( 0,
1963 NodeHelper::fromAsString( node
),
1966 std::vector
<GpgME::Signature
>(),
1970 //kDebug() << "pkcs7 mime - signature found - opaque signed data !";
1974 mNodeHelper
->setSignatureState( signTestNode
, KMMsgFullySigned
);
1975 if ( signTestNode
!= node
)
1976 mNodeHelper
->setSignatureState( node
, KMMsgFullySigned
);
1978 //kDebug() << "pkcs7 mime - NO signature found :-(";
1982 return isSigned
|| isEncrypted
;
1985 bool ObjectTreeParser::decryptChiasmus( const QByteArray
& data
, QByteArray
& bodyDecoded
, QString
& errorText
)
1987 const Kleo::CryptoBackend::Protocol
* chiasmus
=
1988 Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
1992 const std::auto_ptr
<Kleo::SpecialJob
> listjob( chiasmus
->specialJob( "x-obtain-keys", QMap
<QString
,QVariant
>() ) );
1993 if ( !listjob
.get() ) {
1994 errorText
= i18n( "Chiasmus backend does not offer the "
1995 "\"x-obtain-keys\" function. Please report this bug." );
1999 if ( listjob
->exec() ) {
2000 errorText
= i18n( "Chiasmus Backend Error" );
2004 const QVariant result
= listjob
->property( "result" );
2005 if ( result
.type() != QVariant::StringList
) {
2006 errorText
= i18n( "Unexpected return value from Chiasmus backend: "
2007 "The \"x-obtain-keys\" function did not return a "
2008 "string list. Please report this bug." );
2012 const QStringList keys
= result
.toStringList();
2013 if ( keys
.empty() ) {
2014 errorText
= i18n( "No keys have been found. Please check that a "
2015 "valid key path has been set in the Chiasmus "
2020 AutoQPointer
<ChiasmusKeySelector
> selectorDlg( new ChiasmusKeySelector( /*mReader*/0, i18n( "Chiasmus Decryption Key Selection" ),
2021 keys
, GlobalSettings::chiasmusDecryptionKey(),
2022 GlobalSettings::chiasmusDecryptionOptions() ) );
2024 if ( selectorDlg
->exec() != KDialog::Accepted
|| !selectorDlg
) {
2027 GlobalSettings::setChiasmusDecryptionOptions( selectorDlg
->options() );
2028 GlobalSettings::setChiasmusDecryptionKey( selectorDlg
->key() );
2029 assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );
2031 Kleo::SpecialJob
* job
= chiasmus
->specialJob( "x-decrypt", QMap
<QString
,QVariant
>() );
2033 errorText
= i18n( "Chiasmus backend does not offer the "
2034 "\"x-decrypt\" function. Please report this bug." );
2038 if ( !job
->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
2039 !job
->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
2040 !job
->setProperty( "input", data
) ) {
2041 errorText
= i18n( "The \"x-decrypt\" function does not accept "
2042 "the expected parameters. Please report this bug." );
2046 if ( job
->exec() ) {
2047 errorText
= i18n( "Chiasmus Decryption Error" );
2051 const QVariant resultData
= job
->property( "result" );
2052 if ( resultData
.type() != QVariant::ByteArray
) {
2053 errorText
= i18n( "Unexpected return value from Chiasmus backend: "
2054 "The \"x-decrypt\" function did not return a "
2055 "byte array. Please report this bug." );
2058 bodyDecoded
= resultData
.toByteArray();
2062 bool ObjectTreeParser::processApplicationChiasmusTextSubtype( KMime::Content
* curNode
, ProcessResult
& result
)
2064 if ( !htmlWriter() ) {
2065 mRawReplyString
= curNode
->decodedContent();
2066 mTextualContent
+= curNode
->decodedText();
2067 mTextualContentCharset
= NodeHelper::charset( curNode
);
2071 QByteArray decryptedBody
;
2073 const QByteArray data
= curNode
->decodedContent();
2074 bool bOkDecrypt
= decryptChiasmus( data
, decryptedBody
, errorText
);
2075 PartMetaData messagePart
;
2076 messagePart
.isDecryptable
= bOkDecrypt
;
2077 messagePart
.isEncrypted
= true;
2078 messagePart
.isSigned
= false;
2079 messagePart
.errorText
= errorText
;
2081 htmlWriter()->queue( writeSigstatHeader( messagePart
,
2082 0, //cryptPlugWrapper(),
2083 NodeHelper::fromAsString( curNode
) ) );
2084 const QByteArray body
= bOkDecrypt
? decryptedBody
: data
;
2085 const QString chiasmusCharset
= curNode
->contentType()->parameter("chiasmus-charset");
2086 const QTextCodec
* aCodec
= chiasmusCharset
.isEmpty() ? codecFor( curNode
)
2087 : NodeHelper::codecForName( chiasmusCharset
.toAscii() );
2088 htmlWriter()->queue( quotedHTML( aCodec
->toUnicode( body
), false /*decorate*/ ) );
2089 result
.setInlineEncryptionState( KMMsgFullyEncrypted
);
2091 htmlWriter()->queue( writeSigstatFooter( messagePart
) );
2092 mNodeHelper
->setPartMetaData( curNode
, messagePart
);
2096 void ObjectTreeParser::writeBodyString( const QByteArray
& bodyString
,
2097 const QString
& fromAddress
,
2098 const QTextCodec
* codec
,
2099 ProcessResult
& result
,
2102 // FIXME: This is wrong, it means that inline PGP messages will not be decrypted when there is no
2103 // HTML writer. Even if there would be a HTML writer, the decrypted inline PGP text is not
2104 // added to the textual content.
2105 // The solution would be to remove this if statement and make writeBodyStr() add the
2106 // decrypted string to the textual content as well, and removing any manual modifictions
2107 // of the textual content by callers of this method.
2108 if ( !htmlWriter() )
2112 KMMsgSignatureState inlineSignatureState
= result
.inlineSignatureState();
2113 KMMsgEncryptionState inlineEncryptionState
= result
.inlineEncryptionState();
2114 writeBodyStr( bodyString
, codec
, fromAddress
,
2115 inlineSignatureState
, inlineEncryptionState
, decorate
);
2116 result
.setInlineSignatureState( inlineSignatureState
);
2117 result
.setInlineEncryptionState( inlineEncryptionState
);
2120 void ObjectTreeParser::writePartIcon( KMime::Content
* msgPart
, bool inlineImage
)
2122 if ( !htmlWriter() || !msgPart
)
2125 QString label
= NodeHelper::fileName( msgPart
);
2126 if ( label
.isEmpty() )
2127 label
= i18nc( "display name for an unnamed attachment", "Unnamed" );
2128 label
= StringUtil::quoteHtmlChars( label
, true );
2130 QString comment
= msgPart
->contentDescription()->asUnicodeString();
2131 comment
= StringUtil::quoteHtmlChars( comment
, true );
2132 if ( label
== comment
)
2135 QString fileName
= mNodeHelper
->writeNodeToTempFile( msgPart
);
2136 QString href
= mNodeHelper
->asHREF( msgPart
, "body" );
2140 if ( inlineImage
) {
2141 // show the filename of the image below the embedded image
2142 htmlWriter()->queue( "<div><a href=\"" + href
+ "\">"
2143 "<img src=\"file:///" + fileName
+ "\" border=\"0\" style=\"max-width: 100%\"/></a>"
2145 "<div><a href=\"" + href
+ "\">" + label
+ "</a>"
2147 "<div>" + comment
+ "</div><br/>" );
2149 // show the filename next to the image
2150 iconName
= mNodeHelper
->iconName( msgPart
);
2151 if( iconName
.right( 14 ) == "mime_empty.png" ) {
2152 mNodeHelper
->magicSetType( msgPart
);
2153 iconName
= mNodeHelper
->iconName( msgPart
);
2155 htmlWriter()->queue( "<div><a href=\"" + href
+ "\"><img src=\"file:///" +
2156 iconName
+ "\" border=\"0\" style=\"max-width: 100%\" alt=\"\"/>" + label
+
2158 "<div>" + comment
+ "</div><br/>" );
2162 static const int SIG_FRAME_COL_UNDEF
= 99;
2163 #define SIG_FRAME_COL_RED -1
2164 #define SIG_FRAME_COL_YELLOW 0
2165 #define SIG_FRAME_COL_GREEN 1
2166 QString
ObjectTreeParser::sigStatusToString( const Kleo::CryptoBackend::Protocol
* cryptProto
,
2168 GpgME::Signature::Summary summary
,
2170 bool& showKeyInfos
)
2172 // note: At the moment frameColor and showKeyInfos are
2173 // used for CMS only but not for PGP signatures
2174 // pending(khz): Implement usage of these for PGP sigs as well.
2175 showKeyInfos
= true;
2178 if( cryptProto
== Kleo::CryptoBackendFactory::instance()->openpgp() ) {
2179 // process enum according to it's definition to be read in
2180 // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
2181 switch( status_code
) {
2182 case 0: // GPGME_SIG_STAT_NONE
2183 result
= i18n("Error: Signature not verified");
2185 case 1: // GPGME_SIG_STAT_GOOD
2186 result
= i18n("Good signature");
2188 case 2: // GPGME_SIG_STAT_BAD
2189 result
= i18n("<b>Bad</b> signature");
2191 case 3: // GPGME_SIG_STAT_NOKEY
2192 result
= i18n("No public key to verify the signature");
2194 case 4: // GPGME_SIG_STAT_NOSIG
2195 result
= i18n("No signature found");
2197 case 5: // GPGME_SIG_STAT_ERROR
2198 result
= i18n("Error verifying the signature");
2200 case 6: // GPGME_SIG_STAT_DIFF
2201 result
= i18n("Different results for signatures");
2203 /* PENDING(khz) Verify exact meaning of the following values:
2204 case 7: // GPGME_SIG_STAT_GOOD_EXP
2205 return i18n("Signature certificate is expired");
2207 case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
2208 return i18n("One of the certificate's keys is expired");
2212 result
= ""; // do *not* return a default text here !
2216 else if ( cryptProto
== Kleo::CryptoBackendFactory::instance()->smime() ) {
2217 // process status bits according to SigStatus_...
2218 // definitions in kdenetwork/libkdenetwork/cryptplug.h
2220 if( summary
== GpgME::Signature::None
) {
2221 result
= i18n("No status information available.");
2222 frameColor
= SIG_FRAME_COL_YELLOW
;
2223 showKeyInfos
= false;
2227 if( summary
& GpgME::Signature::Valid
) {
2228 result
= i18n("Good signature.");
2230 // Here we are work differently than KMail did before!
2232 // The GOOD case ( == sig matching and the complete
2233 // certificate chain was verified and is valid today )
2234 // by definition does *not* show any key
2235 // information but just states that things are OK.
2236 // (khz, according to LinuxTag 2002 meeting)
2237 frameColor
= SIG_FRAME_COL_GREEN
;
2238 showKeyInfos
= false;
2242 // we are still there? OK, let's test the different cases:
2244 // we assume green, test for yellow or red (in this order!)
2245 frameColor
= SIG_FRAME_COL_GREEN
;
2247 if( summary
& GpgME::Signature::KeyExpired
){
2249 result2
+= i18n("One key has expired.");
2251 if( summary
& GpgME::Signature::SigExpired
){
2252 // and still is green!
2253 result2
+= i18n("The signature has expired.");
2257 if( summary
& GpgME::Signature::KeyMissing
) {
2258 result2
+= i18n("Unable to verify: key missing.");
2259 // if the signature certificate is missing
2260 // we cannot show information on it
2261 showKeyInfos
= false;
2262 frameColor
= SIG_FRAME_COL_YELLOW
;
2264 if( summary
& GpgME::Signature::CrlMissing
){
2265 result2
+= i18n("CRL not available.");
2266 frameColor
= SIG_FRAME_COL_YELLOW
;
2268 if( summary
& GpgME::Signature::CrlTooOld
){
2269 result2
+= i18n("Available CRL is too old.");
2270 frameColor
= SIG_FRAME_COL_YELLOW
;
2272 if( summary
& GpgME::Signature::BadPolicy
){
2273 result2
+= i18n("A policy was not met.");
2274 frameColor
= SIG_FRAME_COL_YELLOW
;
2276 if( summary
& GpgME::Signature::SysError
){
2277 result2
+= i18n("A system error occurred.");
2278 // if a system error occurred
2279 // we cannot trust any information
2280 // that was given back by the plug-in
2281 showKeyInfos
= false;
2282 frameColor
= SIG_FRAME_COL_YELLOW
;
2286 if( summary
& GpgME::Signature::KeyRevoked
){
2288 result2
+= i18n("One key has been revoked.");
2289 frameColor
= SIG_FRAME_COL_RED
;
2291 if( summary
& GpgME::Signature::Red
) {
2292 if( result2
.isEmpty() )
2294 // Here we are work differently than KMail did before!
2296 // The BAD case ( == sig *not* matching )
2297 // by definition does *not* show any key
2298 // information but just states that things are BAD.
2300 // The reason for this: In this case ALL information
2301 // might be falsificated, we can NOT trust the data
2302 // in the body NOT the signature - so we don't show
2303 // any key/signature information at all!
2304 // (khz, according to LinuxTag 2002 meeting)
2305 showKeyInfos
= false;
2306 frameColor
= SIG_FRAME_COL_RED
;
2311 if( SIG_FRAME_COL_GREEN
== frameColor
) {
2312 result
= i18n("Good signature.");
2313 } else if( SIG_FRAME_COL_RED
== frameColor
) {
2314 result
= i18n("<b>Bad</b> signature.");
2318 if( !result2
.isEmpty() ) {
2319 if( !result
.isEmpty() )
2320 result
.append("<br />");
2321 result
.append( result2
);
2325 // add i18n support for 3rd party plug-ins here:
2326 else if ( cryptPlug->libName().contains( "yetanotherpluginname", Qt::CaseInsensitive )) {
2335 static QString
writeSimpleSigstatHeader( const PartMetaData
&block
)
2338 html
+= "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td>";
2340 if ( block
.signClass
== "signErr" ) {
2341 html
+= i18n( "Invalid signature." );
2342 } else if ( block
.signClass
== "signOkKeyBad" || block
.signClass
== "signWarn" ) {
2343 html
+= i18n( "Not enough information to check signature validity." );
2344 } else if ( block
.signClass
== "signOkKeyOk" ) {
2346 if ( !block
.signerMailAddresses
.isEmpty() )
2347 addr
= block
.signerMailAddresses
.first();
2348 QString name
= addr
;
2349 if ( name
.isEmpty() )
2350 name
= block
.signer
;
2351 if ( addr
.isEmpty() ) {
2352 html
+= i18n( "Signature is valid." );
2354 html
+= i18n( "Signed by <a href=\"mailto:%1\">%2</a>.", addr
, name
);
2357 // should not happen
2358 html
+= i18n( "Unknown signature state" );
2360 html
+= "</td><td align=\"right\">";
2361 html
+= "<a href=\"kmail:showSignatureDetails\">";
2362 html
+= i18n( "Show Details" );
2363 html
+= "</a></td></tr></table>";
2367 static QString
beginVerboseSigstatHeader()
2369 return "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td rowspan=\"2\">";
2372 static QString
makeShowAuditLogLink( const GpgME::Error
& err
, const QString
& auditLog
) {
2373 // more or less the same as
2374 // kleopatra/utils/auditlog.cpp:formatLink(), so any bug fixed here
2375 // equally applies there:
2376 if ( const unsigned int code
= err
.code() ) {
2377 if ( code
== GPG_ERR_NOT_IMPLEMENTED
) {
2378 kDebug() << "not showing link (not implemented)";
2380 } else if ( code
== GPG_ERR_NO_DATA
) {
2381 kDebug() << "not showing link (not available)";
2382 return i18n("No Audit Log available");
2384 return i18n("Error Retrieving Audit Log: %1", QString::fromLocal8Bit( err
.asString() ) );
2388 if ( !auditLog
.isEmpty() ) {
2390 url
.setProtocol( "kmail" );
2391 url
.setPath( "showAuditLog" );
2392 url
.addQueryItem( "log", auditLog
);
2394 return "<a href=\"" + url
.url() + "\">" + i18nc("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + "</a>";
2400 static QString
endVerboseSigstatHeader( const PartMetaData
& pmd
)
2403 html
+= "</td><td align=\"right\" valign=\"top\" nowrap=\"nowrap\">";
2404 html
+= "<a href=\"kmail:hideSignatureDetails\">";
2405 html
+= i18n( "Hide Details" );
2406 html
+= "</a></td></tr>";
2407 html
+= "<tr><td align=\"right\" valign=\"bottom\" nowrap=\"nowrap\">";
2408 html
+= makeShowAuditLogLink( pmd
.auditLogError
, pmd
.auditLog
);
2409 html
+= "</td></tr></table>";
2413 QString
ObjectTreeParser::writeSigstatHeader( PartMetaData
& block
,
2414 const Kleo::CryptoBackend::Protocol
* cryptProto
,
2415 const QString
& fromAddress
,
2416 KMime::Content
*node
)
2418 const bool isSMIME
= cryptProto
&& ( cryptProto
== Kleo::CryptoBackendFactory::instance()->smime() );
2419 QString signer
= block
.signer
;
2421 QString htmlStr
, simpleHtmlStr
;
2422 QString dir
= ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
2423 QString
cellPadding("cellpadding=\"1\"");
2425 if( block
.isEncapsulatedRfc822Message
)
2427 htmlStr
+= "<table cellspacing=\"1\" "+cellPadding
+" class=\"rfc822\">"
2428 "<tr class=\"rfc822H\"><td dir=\"" + dir
+ "\">";
2430 htmlStr
+= "<a href=\"" + mNodeHelper
->asHREF( node
, "body" ) + "\">"
2431 + i18n("Encapsulated message") + "</a>";
2433 htmlStr
+= i18n("Encapsulated message");
2435 htmlStr
+= "</td></tr><tr class=\"rfc822B\"><td>";
2438 if( block
.isEncrypted
) {
2439 htmlStr
+= "<table cellspacing=\"1\" "+cellPadding
+" class=\"encr\">"
2440 "<tr class=\"encrH\"><td dir=\"" + dir
+ "\">";
2441 if ( block
.inProgress
) {
2442 htmlStr
+= i18n("Please wait while the message is being decrypted...");
2443 } else if( block
.isDecryptable
) {
2444 htmlStr
+= i18n("Encrypted message");
2446 htmlStr
+= i18n("Encrypted message (decryption not possible)");
2447 if( !block
.errorText
.isEmpty() ) {
2448 htmlStr
+= "<br />" + i18n("Reason: %1", block
.errorText
);
2451 htmlStr
+= "</td></tr><tr class=\"encrB\"><td>";
2454 if ( block
.isSigned
&& block
.inProgress
) {
2455 block
.signClass
= "signInProgress";
2456 htmlStr
+= "<table cellspacing=\"1\" "+cellPadding
+" class=\"signInProgress\">"
2457 "<tr class=\"signInProgressH\"><td dir=\"" + dir
+ "\">";
2458 htmlStr
+= i18n("Please wait while the signature is being verified...");
2459 htmlStr
+= "</td></tr><tr class=\"signInProgressB\"><td>";
2462 simpleHtmlStr
= htmlStr
;
2464 if( block
.isSigned
&& !block
.inProgress
) {
2465 QStringList
& blockAddrs( block
.signerMailAddresses
);
2466 // note: At the moment frameColor and showKeyInfos are
2467 // used for CMS only but not for PGP signatures
2468 // pending(khz): Implement usage of these for PGP sigs as well.
2469 int frameColor
= SIG_FRAME_COL_UNDEF
;
2471 bool onlyShowKeyURL
= false;
2472 bool cannotCheckSignature
= true;
2473 QString statusStr
= sigStatusToString( cryptProto
,
2478 // if needed fallback to english status text
2479 // that was reported by the plugin
2480 if( statusStr
.isEmpty() )
2481 statusStr
= block
.status
;
2482 if( block
.technicalProblem
)
2483 frameColor
= SIG_FRAME_COL_YELLOW
;
2485 switch( frameColor
){
2486 case SIG_FRAME_COL_RED
:
2487 cannotCheckSignature
= false;
2489 case SIG_FRAME_COL_YELLOW
:
2490 cannotCheckSignature
= true;
2492 case SIG_FRAME_COL_GREEN
:
2493 cannotCheckSignature
= false;
2497 // compose the string for displaying the key ID
2498 // either as URL or not linked (for PGP)
2499 // note: Once we can start PGP key manager programs
2500 // from within KMail we could change this and
2501 // always show the URL. (khz, 2002/06/27)
2502 QString startKeyHREF
;
2505 QString("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
2506 .arg( cryptProto
->displayName(),
2508 QString::fromLatin1( block
.keyId
) );
2509 QString keyWithWithoutURL
2510 // FIXME: Kleopatra misses a -query option, so disable this for now.
2512 ? QString("%1%2</a>")
2514 cannotCheckSignature ? i18n("[Details]") : ("0x" + block.keyId) )
2515 : */"0x" + QString::fromUtf8( block
.keyId
);
2518 // temporary hack: always show key information!
2519 showKeyInfos
= true;
2521 // Sorry for using 'black' as null color but .isValid()
2522 // checking with QColor default c'tor did not work for
2524 if( isSMIME
&& (SIG_FRAME_COL_UNDEF
!= frameColor
) ) {
2526 // new frame settings for CMS:
2527 // beautify the status string
2528 if( !statusStr
.isEmpty() ) {
2529 statusStr
.prepend("<i>");
2530 statusStr
.append( "</i>");
2533 // special color handling: S/MIME uses only green/yellow/red.
2534 switch( frameColor
) {
2535 case SIG_FRAME_COL_RED
:
2536 block
.signClass
= "signErr";//"signCMSRed";
2537 onlyShowKeyURL
= true;
2539 case SIG_FRAME_COL_YELLOW
:
2540 if( block
.technicalProblem
)
2541 block
.signClass
= "signWarn";
2543 block
.signClass
= "signOkKeyBad";//"signCMSYellow";
2545 case SIG_FRAME_COL_GREEN
:
2546 block
.signClass
= "signOkKeyOk";//"signCMSGreen";
2547 // extra hint for green case
2548 // that email addresses in DN do not match fromAddress
2549 QString greenCaseWarning
;
2550 QString
msgFrom( KPIMUtils::extractEmailAddress(fromAddress
) );
2551 QString certificate
;
2552 if( block
.keyId
.isEmpty() )
2553 certificate
= i18n("certificate");
2555 certificate
= startKeyHREF
+ i18n("certificate") + "</a>";
2556 if( !blockAddrs
.empty() ){
2557 if( !blockAddrs
.contains( msgFrom
, Qt::CaseInsensitive
) ) {
2560 i18nc("Start of warning message."
2563 i18n("Sender's mail address is not stored "
2564 "in the %1 used for signing.", certificate
) +
2570 // We cannot use Qt's join() function here but
2571 // have to join the addresses manually to
2572 // extract the mail addresses (without '<''>')
2573 // before including it into our string:
2575 for(QStringList::ConstIterator it
= blockAddrs
.constBegin();
2576 it
!= blockAddrs
.constEnd(); ++it
){
2578 greenCaseWarning
.append(", <br /> ");
2580 greenCaseWarning
.append( KPIMUtils::extractEmailAddress(*it
) );
2586 i18nc("Start of warning message.","Warning:") +
2588 i18n("No mail address is stored in the %1 used for signing, "
2589 "so we cannot compare it to the sender's address %2.",
2593 if( !greenCaseWarning
.isEmpty() ) {
2594 if( !statusStr
.isEmpty() )
2595 statusStr
.append("<br /> <br />");
2596 statusStr
.append( greenCaseWarning
);
2601 QString frame
= "<table cellspacing=\"1\" "+cellPadding
+" "
2602 "class=\"" + block
.signClass
+ "\">"
2603 "<tr class=\"" + block
.signClass
+ "H\"><td dir=\"" + dir
+ "\">";
2604 htmlStr
+= frame
+ beginVerboseSigstatHeader();
2605 simpleHtmlStr
+= frame
;
2606 simpleHtmlStr
+= writeSimpleSigstatHeader( block
);
2607 if( block
.technicalProblem
) {
2608 htmlStr
+= block
.errorText
;
2610 else if( showKeyInfos
) {
2611 if( cannotCheckSignature
) {
2612 htmlStr
+= i18n( "Not enough information to check "
2614 keyWithWithoutURL
);
2618 if (block
.signer
.isEmpty())
2621 if( !blockAddrs
.empty() ){
2622 const KUrl address
= KPIMUtils::encodeMailtoUrl( blockAddrs
.first() );
2623 signer
= "<a href=\"mailto:" + KUrl::toPercentEncoding( address
.path() ) +
2624 "\">" + signer
+ "</a>";
2628 if( block
.keyId
.isEmpty() ) {
2629 if( signer
.isEmpty() || onlyShowKeyURL
)
2630 htmlStr
+= i18n( "Message was signed with unknown key." );
2632 htmlStr
+= i18n( "Message was signed by %1.",
2635 QDateTime created
= block
.creationTime
;
2636 if( created
.isValid() ) {
2637 if( signer
.isEmpty() ) {
2638 if( onlyShowKeyURL
)
2639 htmlStr
+= i18n( "Message was signed with key %1.",
2640 keyWithWithoutURL
);
2642 htmlStr
+= i18n( "Message was signed on %1 with key %2.",
2643 KGlobal::locale()->formatDateTime( created
),
2644 keyWithWithoutURL
);
2647 if( onlyShowKeyURL
)
2648 htmlStr
+= i18n( "Message was signed with key %1.",
2649 keyWithWithoutURL
);
2651 htmlStr
+= i18n( "Message was signed by %3 on %1 with key %2",
2652 KGlobal::locale()->formatDateTime( created
),
2658 if( signer
.isEmpty() || onlyShowKeyURL
)
2659 htmlStr
+= i18n( "Message was signed with key %1.",
2660 keyWithWithoutURL
);
2662 htmlStr
+= i18n( "Message was signed by %2 with key %1.",
2668 htmlStr
+= "<br />";
2669 if( !statusStr
.isEmpty() ) {
2670 htmlStr
+= " <br />";
2671 htmlStr
+= i18n( "Status: " );
2672 htmlStr
+= statusStr
;
2675 htmlStr
+= statusStr
;
2677 frame
= "</td></tr><tr class=\"" + block
.signClass
+ "B\"><td>";
2678 htmlStr
+= endVerboseSigstatHeader( block
) + frame
;
2679 simpleHtmlStr
+= frame
;
2683 // old frame settings for PGP:
2685 if( block
.signer
.isEmpty() || block
.technicalProblem
) {
2686 block
.signClass
= "signWarn";
2687 QString frame
= "<table cellspacing=\"1\" "+cellPadding
+" "
2688 "class=\"" + block
.signClass
+ "\">"
2689 "<tr class=\"" + block
.signClass
+ "H\"><td dir=\"" + dir
+ "\">";
2690 htmlStr
+= frame
+ beginVerboseSigstatHeader();
2691 simpleHtmlStr
+= frame
;
2692 simpleHtmlStr
+= writeSimpleSigstatHeader( block
);
2693 if( block
.technicalProblem
) {
2694 htmlStr
+= block
.errorText
;
2697 if( !block
.keyId
.isEmpty() ) {
2698 QDateTime created
= block
.creationTime
;
2699 if ( created
.isValid() )
2700 htmlStr
+= i18n( "Message was signed on %1 with unknown key %2.",
2701 KGlobal::locale()->formatDateTime( created
),
2702 keyWithWithoutURL
);
2704 htmlStr
+= i18n( "Message was signed with unknown key %1.",
2705 keyWithWithoutURL
);
2708 htmlStr
+= i18n( "Message was signed with unknown key." );
2709 htmlStr
+= "<br />";
2710 htmlStr
+= i18n( "The validity of the signature cannot be "
2712 if( !statusStr
.isEmpty() ) {
2713 htmlStr
+= "<br />";
2714 htmlStr
+= i18n( "Status: " );
2716 htmlStr
+= statusStr
;
2720 frame
= "</td></tr><tr class=\"" + block
.signClass
+ "B\"><td>";
2721 htmlStr
+= endVerboseSigstatHeader( block
) + frame
;
2722 simpleHtmlStr
+= frame
;
2726 // HTMLize the signer's user id and create mailto: link
2727 signer
= StringUtil::quoteHtmlChars( signer
, true );
2728 signer
= "<a href=\"mailto:" + signer
+ "\">" + signer
+ "</a>";
2730 if (block
.isGoodSignature
) {
2731 if( block
.keyTrust
< Kpgp::KPGP_VALIDITY_MARGINAL
)
2732 block
.signClass
= "signOkKeyBad";
2734 block
.signClass
= "signOkKeyOk";
2735 QString frame
= "<table cellspacing=\"1\" "+cellPadding
+" "
2736 "class=\"" + block
.signClass
+ "\">"
2737 "<tr class=\"" + block
.signClass
+ "H\"><td dir=\"" + dir
+ "\">";
2738 htmlStr
+= frame
+ beginVerboseSigstatHeader();
2739 simpleHtmlStr
+= frame
;
2740 simpleHtmlStr
+= writeSimpleSigstatHeader( block
);
2741 if( !block
.keyId
.isEmpty() )
2742 htmlStr
+= i18n( "Message was signed by %2 (Key ID: %1).",
2746 htmlStr
+= i18n( "Message was signed by %1.", signer
);
2747 htmlStr
+= "<br />";
2749 switch( block
.keyTrust
)
2751 case Kpgp::KPGP_VALIDITY_UNKNOWN
:
2752 htmlStr
+= i18n( "The signature is valid, but the key's "
2753 "validity is unknown." );
2755 case Kpgp::KPGP_VALIDITY_MARGINAL
:
2756 htmlStr
+= i18n( "The signature is valid and the key is "
2757 "marginally trusted." );
2759 case Kpgp::KPGP_VALIDITY_FULL
:
2760 htmlStr
+= i18n( "The signature is valid and the key is "
2763 case Kpgp::KPGP_VALIDITY_ULTIMATE
:
2764 htmlStr
+= i18n( "The signature is valid and the key is "
2765 "ultimately trusted." );
2768 htmlStr
+= i18n( "The signature is valid, but the key is "
2771 frame
= "</td></tr>"
2772 "<tr class=\"" + block
.signClass
+ "B\"><td>";
2773 htmlStr
+= endVerboseSigstatHeader( block
) + frame
;
2774 simpleHtmlStr
+= frame
;
2778 block
.signClass
= "signErr";
2779 QString frame
= "<table cellspacing=\"1\" "+cellPadding
+" "
2780 "class=\"" + block
.signClass
+ "\">"
2781 "<tr class=\"" + block
.signClass
+ "H\"><td dir=\"" + dir
+ "\">";
2782 htmlStr
+= frame
+ beginVerboseSigstatHeader();
2783 simpleHtmlStr
+= frame
;
2784 simpleHtmlStr
+= writeSimpleSigstatHeader( block
);
2785 if( !block
.keyId
.isEmpty() )
2786 htmlStr
+= i18n( "Message was signed by %2 (Key ID: %1).",
2790 htmlStr
+= i18n( "Message was signed by %1.", signer
);
2791 htmlStr
+= "<br />";
2792 htmlStr
+= i18n("Warning: The signature is bad.");
2793 frame
= "</td></tr>"
2794 "<tr class=\"" + block
.signClass
+ "B\"><td>";
2795 htmlStr
+= endVerboseSigstatHeader( block
) + frame
;
2796 simpleHtmlStr
+= frame
;
2802 if ( mSource
->showSignatureDetails() )
2804 return simpleHtmlStr
;
2807 QString
ObjectTreeParser::writeSigstatFooter( PartMetaData
& block
)
2809 QString dir
= ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
2813 if (block
.isSigned
) {
2814 htmlStr
+= "</td></tr><tr class=\"" + block
.signClass
+ "H\">";
2815 htmlStr
+= "<td dir=\"" + dir
+ "\">" +
2816 i18n( "End of signed message" ) +
2817 "</td></tr></table>";
2820 if (block
.isEncrypted
) {
2821 htmlStr
+= "</td></tr><tr class=\"encrH\"><td dir=\"" + dir
+ "\">" +
2822 i18n( "End of encrypted message" ) +
2823 "</td></tr></table>";
2826 if( block
.isEncapsulatedRfc822Message
)
2828 htmlStr
+= "</td></tr><tr class=\"rfc822H\"><td dir=\"" + dir
+ "\">" +
2829 i18n( "End of encapsulated message" ) +
2830 "</td></tr></table>";
2837 //-----------------------------------------------------------------------------
2839 void ObjectTreeParser::writeAttachmentMarkHeader( KMime::Content
*node
)
2841 if ( !htmlWriter() )
2844 htmlWriter()->queue( QString( "<div id=\"attachmentDiv%1\">\n" ).arg( node
->index().toString() ) );
2847 //-----------------------------------------------------------------------------
2849 void ObjectTreeParser::writeAttachmentMarkFooter()
2851 if ( !htmlWriter() )
2854 htmlWriter()->queue( QString( "</div>" ) );
2859 //-----------------------------------------------------------------------------
2860 void ObjectTreeParser::writeBodyStr( const QByteArray
& aStr
, const QTextCodec
*aCodec
,
2861 const QString
& fromAddress
)
2863 KMMsgSignatureState dummy1
;
2864 KMMsgEncryptionState dummy2
;
2865 writeBodyStr( aStr
, aCodec
, fromAddress
, dummy1
, dummy2
, false );
2868 //-----------------------------------------------------------------------------
2869 void ObjectTreeParser::writeBodyStr( const QByteArray
& aStr
, const QTextCodec
*aCodec
,
2870 const QString
& fromAddress
,
2871 KMMsgSignatureState
& inlineSignatureState
,
2872 KMMsgEncryptionState
& inlineEncryptionState
,
2875 bool goodSignature
= false;
2876 Kpgp::Module
* pgp
= Kpgp::Module::getKpgp();
2878 bool isPgpMessage
= false; // true if the message contains at least one
2879 // PGP MESSAGE or one PGP SIGNED MESSAGE block
2880 QString dir
= ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
2881 QString headerStr
= QString("<div dir=\"%1\">").arg(dir
);
2883 inlineSignatureState
= KMMsgNotSigned
;
2884 inlineEncryptionState
= KMMsgNotEncrypted
;
2885 QList
<Kpgp::Block
> pgpBlocks
;
2886 QList
<QByteArray
> nonPgpBlocks
;
2887 if( Kpgp::Module::prepareMessageForDecryption( aStr
, pgpBlocks
, nonPgpBlocks
) )
2889 bool isEncrypted
= false, isSigned
= false;
2890 bool fullySignedOrEncrypted
= true;
2891 bool firstNonPgpBlock
= true;
2892 bool couldDecrypt
= false;
2895 QString decryptionError
;
2896 Kpgp::Validity keyTrust
= Kpgp::KPGP_VALIDITY_FULL
;
2898 QList
<Kpgp::Block
>::iterator pbit
= pgpBlocks
.begin();
2899 QListIterator
<QByteArray
> npbit( nonPgpBlocks
);
2901 for( ; pbit
!= pgpBlocks
.end(); ++pbit
)
2903 // insert the next Non-OpenPGP block
2904 QByteArray
str( npbit
.next() );
2905 if( !str
.isEmpty() ) {
2906 htmlStr
+= quotedHTML( aCodec
->toUnicode( str
), decorate
);
2907 kDebug() << "Non-empty Non-OpenPGP block found: '" << str
<< "'";
2908 // treat messages with empty lines before the first clearsigned
2909 // block as fully signed/encrypted
2910 if( firstNonPgpBlock
) {
2911 // check whether str only consists of \n
2912 for( QByteArray::ConstIterator c
= str
.begin(); *c
; ++c
) {
2914 fullySignedOrEncrypted
= false;
2920 fullySignedOrEncrypted
= false;
2923 firstNonPgpBlock
= false;
2925 //htmlStr += "<br>";
2927 Kpgp::Block
&block
= *pbit
;
2928 if( ( block
.type() == Kpgp::PgpMessageBlock
/*FIXME(Andras) port to akonadi
2930 // ### Workaround for bug 56693
2931 !kmkernel->contextMenuShown() */) ||
2932 ( block
.type() == Kpgp::ClearsignedBlock
) )
2934 isPgpMessage
= true;
2935 if( block
.type() == Kpgp::PgpMessageBlock
)
2937 // try to decrypt this OpenPGP block
2938 couldDecrypt
= block
.decrypt();
2939 isEncrypted
= block
.isEncrypted();
2940 if (!couldDecrypt
) {
2941 decryptionError
= pgp
->lastErrorMsg();
2946 // try to verify this OpenPGP block
2950 isSigned
= block
.isSigned();
2953 keyId
= block
.signatureKeyId();
2954 signer
= block
.signatureUserId();
2955 if( !signer
.isEmpty() )
2957 goodSignature
= block
.goodSignature();
2959 if( !keyId
.isEmpty() ) {
2960 keyTrust
= pgp
->keyTrust( keyId
);
2961 Kpgp::Key
* key
= pgp
->publicKey( keyId
);
2963 // Use the user ID from the key because this one
2965 signer
= key
->primaryUserID();
2969 // This is needed for the PGP 6 support because PGP 6 doesn't
2970 // print the key id of the signing key if the key is known.
2971 keyTrust
= pgp
->keyTrust( signer
);
2976 inlineSignatureState
= KMMsgPartiallySigned
;
2978 inlineEncryptionState
= KMMsgPartiallyEncrypted
;
2980 PartMetaData messagePart
;
2982 messagePart
.isSigned
= isSigned
;
2983 messagePart
.technicalProblem
= false;
2984 messagePart
.isGoodSignature
= goodSignature
;
2985 messagePart
.isEncrypted
= isEncrypted
;
2986 messagePart
.isDecryptable
= couldDecrypt
;
2987 messagePart
.decryptionError
= decryptionError
;
2988 messagePart
.signer
= signer
;
2989 messagePart
.keyId
= keyId
;
2990 messagePart
.keyTrust
= keyTrust
;
2991 messagePart
.auditLogError
= GpgME::Error( GPG_ERR_NOT_IMPLEMENTED
);
2993 htmlStr
+= writeSigstatHeader( messagePart
, 0, fromAddress
);
2995 if ( couldDecrypt
|| !isEncrypted
) {
2996 htmlStr
+= quotedHTML( aCodec
->toUnicode( block
.text() ), decorate
);
2999 htmlStr
+= QString( "<div align=\"center\">%1</div>" )
3000 .arg( i18n( "The message could not be decrypted.") );
3002 htmlStr
+= writeSigstatFooter( messagePart
);
3004 else // block is neither message block nor clearsigned block
3005 htmlStr
+= quotedHTML( aCodec
->toUnicode( block
.text() ),
3009 // add the last Non-OpenPGP block
3010 QByteArray
str( nonPgpBlocks
.last() );
3011 if( !str
.isEmpty() ) {
3012 htmlStr
+= quotedHTML( aCodec
->toUnicode( str
), decorate
);
3013 // Even if the trailing Non-OpenPGP block isn't empty we still
3014 // consider the message part fully signed/encrypted because else
3015 // all inline signed mailing list messages would only be partially
3016 // signed because of the footer which is often added by the mailing
3017 // list software. IK, 2003-02-15
3019 if( fullySignedOrEncrypted
) {
3020 if( inlineSignatureState
== KMMsgPartiallySigned
)
3021 inlineSignatureState
= KMMsgFullySigned
;
3022 if( inlineEncryptionState
== KMMsgPartiallyEncrypted
)
3023 inlineEncryptionState
= KMMsgFullyEncrypted
;
3025 htmlWriter()->queue( htmlStr
);
3028 htmlWriter()->queue( quotedHTML( aCodec
->toUnicode( aStr
), decorate
) );
3032 QString
ObjectTreeParser::quotedHTML( const QString
& s
, bool decorate
)
3034 assert( cssHelper() );
3036 int convertFlags
= LinkLocator::PreserveSpaces
| LinkLocator::HighlightText
;
3037 if ( decorate
&& GlobalSettings::self()->showEmoticons() ) {
3038 convertFlags
|= LinkLocator::ReplaceSmileys
;
3041 const QString normalStartTag
= cssHelper()->nonQuotedFontTag();
3042 QString quoteFontTag
[3];
3043 QString deepQuoteFontTag
[3];
3044 for ( int i
= 0 ; i
< 3 ; ++i
) {
3045 quoteFontTag
[i
] = cssHelper()->quoteFontTag( i
);
3046 deepQuoteFontTag
[i
] = cssHelper()->quoteFontTag( i
+3 );
3048 const QString normalEndTag
= "</div>";
3049 const QString quoteEnd
= "</div>";
3051 const unsigned int length
= s
.length();
3052 bool paraIsRTL
= false;
3053 bool startNewPara
= true;
3054 unsigned int pos
, beg
;
3056 // skip leading empty lines
3057 for ( pos
= 0; pos
< length
&& s
[pos
] <= ' '; pos
++ )
3059 while (pos
> 0 && (s
[pos
-1] == ' ' || s
[pos
-1] == '\t')) pos
--;
3062 int currQuoteLevel
= -2; // -2 == no previous lines
3063 bool curHidden
= false; // no hide any block
3065 if ( GlobalSettings::self()->showExpandQuotesMark() )
3068 if ( mCollapseIcon
.isEmpty() ) {
3069 mCollapseIcon
= LinkLocator::pngToDataUrl(
3070 IconNameCache::instance()->iconPath( "quotecollapse", 0 ));
3072 if ( mExpandIcon
.isEmpty() )
3073 mExpandIcon
= LinkLocator::pngToDataUrl(
3074 IconNameCache::instance()->iconPath( "quoteexpand", 0 ));
3081 /* search next occurrence of '\n' */
3082 pos
= s
.indexOf('\n', beg
, Qt::CaseInsensitive
);
3083 if (pos
== (unsigned int)(-1))
3086 line
= s
.mid(beg
,pos
-beg
);
3089 /* calculate line's current quoting depth */
3090 int actQuoteLevel
= -1;
3092 for (int p
=0; p
<line
.length(); p
++) {
3093 switch (line
[p
].toLatin1()) {
3098 case ' ': // spaces and tabs are allowed between the quote markers
3102 default: // stop quoting depth calculation
3108 bool actHidden
= false;
3110 // This quoted line needs be hidden
3111 if (GlobalSettings::self()->showExpandQuotesMark() && mSource
->levelQuote() >= 0
3112 && mSource
->levelQuote() <= ( actQuoteLevel
) )
3115 if ( actQuoteLevel
!= currQuoteLevel
) {
3116 /* finish last quotelevel */
3117 if (currQuoteLevel
== -1) {
3118 htmlStr
.append( normalEndTag
);
3119 } else if ( currQuoteLevel
>= 0 && !curHidden
) {
3120 htmlStr
.append( quoteEnd
);
3123 /* start new quotelevel */
3124 if (actQuoteLevel
== -1) {
3125 htmlStr
+= normalStartTag
;
3127 if ( GlobalSettings::self()->showExpandQuotesMark() ) {
3129 //only show the QuoteMark when is the first line of the level hidden
3132 htmlStr
+= "<div class=\"quotelevelmark\" >" ;
3133 htmlStr
+= QString( "<a href=\"kmail:levelquote?%1 \">"
3134 "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
3136 .arg( mExpandIcon
);
3137 htmlStr
+= "</div><br/>";
3138 htmlStr
+= quoteEnd
;
3141 htmlStr
+= "<div class=\"quotelevelmark\" >" ;
3142 htmlStr
+= QString( "<a href=\"kmail:levelquote?%1 \">"
3143 "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
3145 .arg( mCollapseIcon
);
3146 htmlStr
+= "</div>";
3147 if ( actQuoteLevel
< 3 ) {
3148 htmlStr
+= quoteFontTag
[actQuoteLevel
];
3150 htmlStr
+= deepQuoteFontTag
[actQuoteLevel
%3];
3154 if ( actQuoteLevel
< 3 ) {
3155 htmlStr
+= quoteFontTag
[actQuoteLevel
];
3157 htmlStr
+= deepQuoteFontTag
[actQuoteLevel
%3];
3161 currQuoteLevel
= actQuoteLevel
;
3163 curHidden
= actHidden
;
3168 // don't write empty <div ...></div> blocks (they have zero height)
3169 // ignore ^M DOS linebreaks
3170 if( !line
.remove( '\015' ).isEmpty() )
3173 paraIsRTL
= line
.isRightToLeft();
3174 htmlStr
+= QString( "<div dir=\"%1\">" ).arg( paraIsRTL
? "rtl" : "ltr" );
3175 htmlStr
+= LinkLocator::convertToHtml( line
, convertFlags
);
3176 htmlStr
+= QString( "</div>" );
3177 startNewPara
= looksLikeParaBreak( s
, pos
);
3182 // after an empty line, always start a new paragraph
3183 startNewPara
= true;
3188 /* really finish the last quotelevel */
3189 if (currQuoteLevel
== -1) {
3190 htmlStr
.append( normalEndTag
);
3192 htmlStr
.append( quoteEnd
);
3195 //kDebug() << "========================================\n"
3197 // << "\n======================================\n";
3203 const QTextCodec
* ObjectTreeParser::codecFor( KMime::Content
* node
) const
3206 if ( mSource
->overrideCodec() )
3207 return mSource
->overrideCodec();
3208 return mNodeHelper
->codec( node
);
3211 // Guesstimate if the newline at newLinePos actually separates paragraphs in the text s
3212 // We use several heuristics:
3213 // 1. If newLinePos points after or before (=at the very beginning of) text, it is not between paragraphs
3214 // 2. If the previous line was longer than the wrap size, we want to consider it a paragraph on its own
3215 // (some clients, notably Outlook, send each para as a line in the plain-text version).
3216 // 3. Otherwise, we check if the newline could have been inserted for wrapping around; if this
3217 // was the case, then the previous line will be shorter than the wrap size (which we already
3218 // know because of item 2 above), but adding the first word from the next line will make it
3219 // longer than the wrap size.
3220 bool ObjectTreeParser::looksLikeParaBreak( const QString
& s
, unsigned int newLinePos
) const
3222 const unsigned int WRAP_COL
= 78;
3224 unsigned int length
= s
.length();
3225 // 1. Is newLinePos at an end of the text?
3226 if ( newLinePos
>= length
-1 || newLinePos
== 0 ) {
3230 // 2. Is the previous line really a paragraph -- longer than the wrap size?
3232 // First char of prev line -- works also for first line
3233 unsigned prevStart
= s
.lastIndexOf( '\n', newLinePos
- 1 ) + 1;
3234 unsigned prevLineLength
= newLinePos
- prevStart
;
3235 if ( prevLineLength
> WRAP_COL
) {
3239 // find next line to delimit search for first word
3240 unsigned int nextStart
= newLinePos
+ 1;
3241 int nextEnd
= s
.indexOf( '\n', nextStart
);
3242 if ( nextEnd
== -1 ) {
3245 QString nextLine
= s
.mid( nextStart
, nextEnd
- nextStart
);
3246 length
= nextLine
.length();
3247 // search for first word in next line
3248 unsigned int wordStart
;
3250 for ( wordStart
= 0; !found
&& wordStart
< length
; wordStart
++ ) {
3251 switch ( nextLine
[wordStart
].toLatin1() ) {
3254 case ' ': // spaces, tabs and quote markers don't count
3265 // next line is essentially empty, it seems -- empty lines are
3269 //Find end of first word.
3270 //Note: flowText (in kmmessage.cpp) separates words for wrap by
3271 //spaces only. This should be consistent, which calls for some
3273 int wordEnd
= nextLine
.indexOf( ' ', wordStart
);
3274 if ( wordEnd
== (-1) ) {
3277 int wordLength
= wordEnd
- wordStart
;
3279 // 3. If adding a space and the first word to the prev line don't
3280 // make it reach the wrap column, then the break was probably
3282 return prevLineLength
+ wordLength
+ 1 < WRAP_COL
;
3286 void ObjectTreeParser::dumpToFile( const char * filename
, const char * start
,
3290 QFile
f( filename
);
3291 if ( f
.open( QIODevice::WriteOnly
) ) {
3293 QDataStream
ds( &f
);
3294 ds
.writeRawData( start
, len
);
3296 f
.close(); // If data is 0 we just create a zero length file.
3302 KMime::Content
* ObjectTreeParser::findType( KMime::Content
* content
, const QByteArray
& mimeType
, bool deep
, bool wide
)
3304 if( ( !content
->contentType()->isEmpty() )
3305 && ( mimeType
.isEmpty() || ( mimeType
== content
->contentType()->mimeType() ) ) )
3307 KMime::Content
*child
= MessageCore::NodeHelper::firstChild( content
);
3308 if ( child
&& deep
) //first child
3309 return findType( child
, mimeType
, deep
, wide
);
3311 KMime::Content
*next
= MessageCore::NodeHelper::nextSibling( content
);
3312 if (next
&& wide
) //next on the same level
3313 return findType( next
, mimeType
, deep
, wide
);
3318 KMime::Content
* ObjectTreeParser::findType( KMime::Content
* content
, const QByteArray
& mediaType
, const QByteArray
& subType
, bool deep
, bool wide
)
3320 if ( !content
->contentType()->isEmpty() ) {
3321 if ( ( mediaType
.isEmpty() || mediaType
== content
->contentType()->mediaType() )
3322 && ( subType
.isEmpty() || subType
== content
->contentType()->subType() ) )
3325 KMime::Content
*child
= MessageCore::NodeHelper::firstChild( content
);
3326 if ( child
&& deep
) //first child
3327 return findType( child
, mediaType
, subType
, deep
, wide
);
3329 KMime::Content
*next
= MessageCore::NodeHelper::nextSibling( content
);
3330 if (next
&& wide
) //next on the same level
3331 return findType( next
, mediaType
, subType
, deep
, wide
);
3336 KMime::Content
* ObjectTreeParser::findTypeNot( KMime::Content
* content
, const QByteArray
& mediaType
, const QByteArray
& subType
, bool deep
, bool wide
)
3338 if( ( !content
->contentType()->isEmpty() )
3339 && ( mediaType
.isEmpty() || content
->contentType()->mediaType() != mediaType
)
3340 && ( subType
.isEmpty() || content
->contentType()->subType() != subType
)
3343 KMime::Content
*child
= MessageCore::NodeHelper::firstChild( content
);
3344 if ( child
&& deep
)
3345 return findTypeNot( child
, mediaType
, subType
, deep
, wide
);
3347 KMime::Content
*next
= MessageCore::NodeHelper::nextSibling( content
);
3349 return findTypeNot( next
, mediaType
, subType
, deep
, wide
);