Fix nepomukindexer usage, add minimal error handling.
[kdepim.git] / messageviewer / objecttreeparser.cpp
blob3915e6e1759cb10c1a4d62d096bd2d74a4ff95a6
1 /* -*- mode: C++; c-file-style: "gnu" -*-
2 objecttreeparser.cpp
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
31 your version.
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"
42 #include "viewer_p.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"
53 #include "util.h"
54 #include "kleojobexecutor.h"
55 #include "nodehelper.h"
56 #include "iconnamecache.h"
57 #include "htmlquotecolorer.h"
58 #include "chiasmuskeyselector.h"
60 // KDEPIM includes
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>
69 #include <kleo/dn.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>
80 #include <gpgme.h>
81 #include <kmime/kmime_message.h>
82 #include <kmime/kmime_util.h>
84 // KDE includes
85 #include <kdebug.h>
86 #include <klocale.h>
87 #include <kmimetype.h>
88 #include <kglobal.h>
89 #include <ktemporaryfile.h>
90 #include <kstandarddirs.h>
91 #include <kiconloader.h>
92 #include <kcodecs.h>
93 #include <kconfiggroup.h>
94 #include <kstyle.h>
96 // Qt includes
97 #include <QApplication>
98 #include <QDir>
99 #include <QFile>
100 #include <QTextCodec>
101 #include <QByteArray>
102 #include <QBuffer>
103 #include <QPixmap>
104 #include <QPainter>
105 #include <QPointer>
107 // other includes
108 #include <sstream>
109 #include <sys/stat.h>
110 #include <sys/types.h>
111 #include <unistd.h>
112 #include <memory>
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;
123 public:
124 CryptoProtocolSaver( ObjectTreeParser * _otp, const Kleo::CryptoBackend::Protocol* _w )
125 : otp( _otp ), protocol( _otp ? _otp->cryptoProtocol() : 0 )
127 if ( otp )
128 otp->setCryptoProtocol( _w );
131 ~CryptoProtocolSaver() {
132 if ( otp )
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 )
153 init();
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 )
162 : mSource( source ),
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 )
174 init();
177 void ObjectTreeParser::init()
179 assert( mSource );
180 if ( !attachmentStrategy() )
181 mAttachmentStrategy = mSource->attachmentStrategy();
183 if ( !mNodeHelper ) {
184 mNodeHelper = new NodeHelper();
185 mDeleteNodeHelper = true;
186 } else {
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 ) {
210 delete mNodeHelper;
211 mNodeHelper = 0;
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 ) );
221 newNode->parse();
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 )
253 if ( !node )
254 return;
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 ) ) {
281 continue;
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 );
311 switch ( result ) {
312 case Interface::BodyPartFormatter::AsIcon:
313 processResult.setNeverDisplayInline( true );
314 mNodeHelper->setNodeDisplayedEmbedded( node, false );
315 // fall through:
316 case Interface::BodyPartFormatter::Failed:
317 defaultHandling( node, processResult );
318 break;
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
328 } else {
329 const BodyPartFormatter * bpf
330 = BodyPartFormatter::createFor( mediaType, subType );
331 if ( !bpf ) {
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() )
346 break;
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()";
358 return;
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 );
369 return;
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 );
377 return;
380 bool asIcon = true;
381 if ( !result.neverDisplayInline() )
382 if ( as )
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() )
388 asIcon = false;
390 // neither image nor text -> show as icon
391 if ( !result.isImage()
392 && !node->contentType()->isText() )
393 asIcon = true;
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() )
398 asIcon = true;
401 if ( asIcon ) {
402 if ( !( as && as->defaultDisplay( node ) == AttachmentStrategy::None ) ||
403 showOnlyOneMimePart() ) {
404 // Write the node as icon only
405 writePartIcon( node );
406 } else {
407 mNodeHelper->setNodeDisplayedHidden( node, true );
409 } else if ( result.isImage() ) {
410 // Embed the image
411 mNodeHelper->setNodeDisplayedEmbedded( node, true );
412 writePartIcon( node, true );
413 } else {
414 mNodeHelper->setNodeDisplayedEmbedded( node, true );
415 writeBodyString( node->decodedContent(),
416 NodeHelper::fromAsString( node ),
417 codecFor( node ), result, false );
419 // end of ###
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() );
430 //////////////////
431 //////////////////
432 //////////////////
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;
449 default:
450 return GPGME_SIG_STAT_ERROR;
454 bool ObjectTreeParser::writeOpaqueOrMultipartSignedData( KMime::Content* data,
455 KMime::Content& sign,
456 const QString& fromAddress,
457 bool doCheck,
458 QByteArray* cleartextData,
459 const std::vector<GpgME::Signature> & paramSignatures,
460 bool hideErrors )
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;
471 if ( cryptProto ) {
472 cryptPlugLibName = cryptProto->name();
473 cryptPlugDisplayName = cryptProto->displayName();
476 #ifdef DEBUG_SIGNATURE
477 #ifndef NDEBUG
478 if ( !doCheck )
479 kDebug() << "showing OpenPGP (Encrypted+Signed) data";
480 else
481 if ( data )
482 kDebug() << "processing Multipart Signed data";
483 else
484 kDebug() << "processing Opaque Signed data";
485 #endif
487 if ( doCheck && cryptProto ) {
488 //kDebug() << "going to call CRYPTPLUG" << cryptPlugLibName;
490 #endif
492 QByteArray cleartext;
493 QByteArray signaturetext;
495 if ( doCheck && cryptProto ) {
496 if ( data ) {
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)";
507 #endif
508 cleartext = KMime::LFtoCRLF( cleartext );
509 #ifdef DEBUG_SIGNATURE
510 kDebug() << " done.";
511 #endif
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;
523 if ( !doCheck )
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;
536 GpgME::Key key;
538 if ( doCheck && cryptProto ) {
539 #ifdef DEBUG_SIGNATURE
540 kDebug() << "tokoe: doCheck and cryptProto";
541 #endif
542 GpgME::VerificationResult result;
543 if ( data ) { // detached
544 #ifdef DEBUG_SIGNATURE
545 kDebug() << "tokoe: is detached signature";
546 #endif
547 const VerifyDetachedBodyPartMemento * m
548 = dynamic_cast<VerifyDetachedBodyPartMemento*>( mNodeHelper->bodyPartMemento( &sign, "verifydetached" ) );
549 if ( !m ) {
550 #ifdef DEBUG_SIGNATURE
551 kDebug() << "tokoe: no memento available";
552 #endif
553 Kleo::VerifyDetachedJob * job = cryptProto->verifyDetachedJob();
554 if ( !job ) {
555 cryptPlugError = CANT_VERIFY_SIGNATURES;
556 // PENDING(marc) cryptProto = 0 here?
557 } else {
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";
564 #endif
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";
570 #endif
571 messagePart.inProgress = true;
572 mHasPendingAsyncJobs = true;
573 } else {
574 m = newM;
576 } else {
577 newM->exec();
578 m = newM;
580 mNodeHelper->setBodyPartMemento( &sign, "verifydetached", newM );
582 } else if ( m->isRunning() ) {
583 #ifdef DEBUG_SIGNATURE
584 kDebug() << "tokoe: memento is running";
585 #endif
586 messagePart.inProgress = true;
587 mHasPendingAsyncJobs = true;
588 m = 0;
591 if ( m ) {
592 #ifdef DEBUG_SIGNATURE
593 kDebug() << "tokoe: memento finished, assign result";
594 #endif
595 result = m->verifyResult();
596 messagePart.auditLogError = m->auditLogError();
597 messagePart.auditLog = m->auditLogAsHtml();
598 key = m->signingKey();
600 } else { // opaque
601 #ifdef DEBUG_SIGNATURE
602 kDebug() << "tokoe: is opaque signature";
603 #endif
604 const VerifyOpaqueBodyPartMemento * m
605 = dynamic_cast<VerifyOpaqueBodyPartMemento*>( mNodeHelper->bodyPartMemento( &sign, "verifyopaque" ) );
606 if ( !m ) {
607 #ifdef DEBUG_SIGNATURE
608 kDebug() << "tokoe: no memento available";
609 #endif
610 Kleo::VerifyOpaqueJob * job = cryptProto->verifyOpaqueJob();
611 if ( !job ) {
612 cryptPlugError = CANT_VERIFY_SIGNATURES;
613 // PENDING(marc) cryptProto = 0 here?
614 } else {
615 VerifyOpaqueBodyPartMemento * newM
616 = new VerifyOpaqueBodyPartMemento( job, cryptProto->keyListJob(), signaturetext );
617 if ( allowAsync() ) {
618 #ifdef DEBUG_SIGNATURE
619 kDebug() << "tokoe: allowAsync";
620 #endif
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";
626 #endif
627 messagePart.inProgress = true;
628 mHasPendingAsyncJobs = true;
629 } else {
630 m = newM;
632 } else {
633 newM->exec();
634 m = newM;
636 mNodeHelper->setBodyPartMemento( &sign, "verifyopaque", newM );
638 } else if ( m->isRunning() ) {
639 #ifdef DEBUG_SIGNATURE
640 kDebug() << "tokoe: memento is running";
641 #endif
642 messagePart.inProgress = true;
643 mHasPendingAsyncJobs = true;
644 m = 0;
647 if ( m ) {
648 #ifdef DEBUG_SIGNATURE
649 kDebug() << "tokoe: memento finished, assign result";
650 #endif
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;
659 ss << result;
660 #ifdef DEBUG_SIGNATURE
661 kDebug() << ss.str().c_str();
662 #endif
663 signatures = result.signatures();
665 else
666 messagePart.auditLogError = GpgME::Error( GPG_ERR_NOT_IMPLEMENTED );
668 #ifdef DEBUG_SIGNATURE
669 if ( doCheck )
670 kDebug() << "returned from CRYPTPLUG";
671 #endif
673 // ### only one signature supported
674 if ( !signatures.empty() ) {
675 #ifdef DEBUG_SIGNATURE
676 kDebug() << "\nFound signature";
677 #endif
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();
694 if ( key.keyID() )
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() );
718 else
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();
726 else
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;
734 #endif
735 } else {
736 messagePart.creationTime = QDateTime();
739 if ( !doCheck || !data ){
740 if ( cleartextData || !cleartext.isEmpty() ) {
741 if ( htmlWriter() )
742 htmlWriter()->queue( writeSigstatHeader( messagePart,
743 cryptProto,
744 fromAddress ) );
745 bIsOpaqueSigned = true;
747 CryptoProtocolSaver cpws( this, cryptProto );
748 createAndParseTempNode( data, doCheck ? cleartext.data() : cleartextData->data(),
749 "opaque signed data" );
751 if ( htmlWriter() )
752 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
755 else if ( !hideErrors ) {
756 QString txt;
757 txt = "<hr><b><h2>";
758 txt.append( i18n( "The crypto engine returned no cleartext data." ) );
759 txt.append( "</h2></b>" );
760 txt.append( "<br/>&nbsp;<br/>" );
761 txt.append( i18n( "Status: " ) );
762 if ( !messagePart.status.isEmpty() ) {
763 txt.append( "<i>" );
764 txt.append( messagePart.status );
765 txt.append( "</i>" );
767 else
768 txt.append( i18nc("Status of message unknown.","(unknown)") );
769 if ( htmlWriter() )
770 htmlWriter()->queue(txt);
773 else {
774 if ( htmlWriter() ) {
775 if ( !cryptProto ) {
776 QString errorMsg;
777 switch ( cryptPlugError ) {
778 case NOT_INITIALIZED:
779 errorMsg = i18n( "Crypto plug-in \"%1\" is not initialized.",
780 cryptPlugLibName );
781 break;
782 case CANT_VERIFY_SIGNATURES:
783 errorMsg = i18n( "Crypto plug-in \"%1\" cannot verify signatures.",
784 cryptPlugLibName );
785 break;
786 case NO_PLUGIN:
787 if ( cryptPlugDisplayName.isEmpty() )
788 errorMsg = i18n( "No appropriate crypto plug-in was found." );
789 else
790 errorMsg = i18nc( "%1 is either 'OpenPGP' or 'S/MIME'",
791 "No %1 plug-in was found.",
792 cryptPlugDisplayName );
793 break;
795 messagePart.errorText = i18n( "The message is signed, but the "
796 "validity of the signature cannot be "
797 "verified.<br />"
798 "Reason: %1",
799 errorMsg );
802 htmlWriter()->queue( writeSigstatHeader( messagePart,
803 cryptProto,
804 fromAddress ) );
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();
815 if ( htmlWriter() )
816 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
818 #ifdef DEBUG_SIGNATURE
819 kDebug() << "done, returning" << ( bIsOpaqueSigned ? "TRUE" : "FALSE" );
820 #endif
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.")
832 + "</div>"
833 "<div style=\"text-align:center; padding-bottom:20pt;\">"
834 "<a href=\"kmail:decryptMessage\">"
835 "<img src=\"" + iconName.toUtf8() + "\"/>"
836 + i18n("Decrypt Message")
837 + "</a></div>";
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,
846 cryptoProtocol(),
847 QString() ) );
848 htmlWriter()->queue( decryptedData );
849 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
854 void ObjectTreeParser::writeDecryptionInProgressBlock()
856 if ( !htmlWriter() )
857 return;
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,
867 cryptoProtocol(),
868 QString() ) );
869 //htmlWriter()->queue( decryptedData );
870 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
873 void ObjectTreeParser::writeCertificateImportResult( const GpgME::ImportResult & res )
875 if ( res.error() ) {
876 htmlWriter()->queue( i18n( "Sorry, certificate could not be imported.<br />"
877 "Reason: %1", QString::fromLocal8Bit( res.error().asString() ) ) );
878 return;
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." ) );
887 return;
889 QString comment = "<b>" + i18n( "Certificate import status:" ) + "</b><br/>&nbsp;<br/>";
890 if ( nImp )
891 comment += i18np( "1 new certificate was imported.",
892 "%1 new certificates were imported.", nImp ) + "<br/>";
893 if ( nUnc )
894 comment += i18np( "1 certificate was unchanged.",
895 "%1 certificates were unchanged.", nUnc ) + "<br/>";
896 if ( nSKImp )
897 comment += i18np( "1 new secret key was imported.",
898 "%1 new secret keys were imported.", nSKImp ) + "<br/>";
899 if ( nSKUnc )
900 comment += i18np( "1 secret key was unchanged.",
901 "%1 secret keys were unchanged.", nSKUnc ) + "<br/>";
902 comment += "&nbsp;<br/>";
903 htmlWriter()->queue( comment );
904 if ( !nImp && !nSKImp ) {
905 htmlWriter()->queue( "<hr>" );
906 return;
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>" );
911 return;
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() ) );
921 } else {
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,
936 bool showWarning,
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;
954 if ( cryptProto )
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();
963 #ifdef MARCS_DEBUG
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() );
971 QString deb;
972 deb = "\n\nE N C R Y P T E D D A T A = ";
973 if ( cipherIsBinary )
974 deb += "[binary data]";
975 else {
976 deb += "\"";
977 deb += cipherStr;
978 deb += "\"";
980 deb += "\n\n";
981 kDebug() << deb;
982 #endif
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" ) );
990 if ( !m ) {
991 Kleo::DecryptVerifyJob * job = cryptProto->decryptVerifyJob();
992 if ( !job ) {
993 cryptPlugError = CANT_DECRYPT;
994 cryptProto = 0;
995 } else {
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;
1004 } else {
1005 m = newM;
1007 } else {
1008 newM->exec();
1009 m = newM;
1011 mNodeHelper->setBodyPartMemento( &data, "decryptverify", newM );
1013 } else if ( m->isRunning() ) {
1014 decryptionStarted = true;
1015 mHasPendingAsyncJobs = true;
1016 m = 0;
1019 if ( m ) {
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;"
1044 "padding:20pt;\">"
1045 + errorMsg.toUtf8()
1046 + "</div>";
1047 if ( !passphraseError )
1048 partMetaData.errorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName )
1049 + "<br />"
1050 + i18n("Error: %1", partMetaData.errorText );
1055 if ( !cryptProto ) {
1056 decryptedData = "<div style=\"text-align:center; padding:20pt;\">"
1057 + errorMsg.toUtf8()
1058 + "</div>";
1059 switch ( cryptPlugError ) {
1060 case NOT_INITIALIZED:
1061 partMetaData.errorText = i18n( "Crypto plug-in \"%1\" is not initialized.",
1062 cryptPlugLibName );
1063 break;
1064 case CANT_DECRYPT:
1065 partMetaData.errorText = i18n( "Crypto plug-in \"%1\" cannot decrypt messages.",
1066 cryptPlugLibName );
1067 break;
1068 case NO_PLUGIN:
1069 partMetaData.errorText = i18n( "No appropriate crypto plug-in was found." );
1070 break;
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;
1084 else {
1085 decryptedData = "<div style=\"font-size:x-large; text-align:center;"
1086 "padding:20pt;\">"
1087 + errorMsg.toUtf8()
1088 + "</div>";
1092 dumpToFile( "dat_05_reader.decrypted", decryptedData.data(), decryptedData.size() );
1094 return bDecryptionOk;
1097 //static
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"
1109 if ( pos > 5 ) {
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 ) )
1121 return true;
1124 // find next occurrence of "http: or "https:
1125 if ( pos == httpPos ) {
1126 httpPos = str.indexOf( "\"http:", httpPos + 6, Qt::CaseInsensitive );
1128 else {
1129 httpsPos = str.indexOf( "\"https:", httpsPos + 7, Qt::CaseInsensitive );
1132 return false;
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() )
1145 return true;
1147 QString bodyText;
1148 if ( mSource->htmlMail() )
1149 bodyText = codecFor( curNode )->toUnicode( partBody );
1150 else
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/>" );
1181 } else {
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 );
1197 return true;
1199 return false;
1202 bool ObjectTreeParser::isMailmanMessage( KMime::Content * curNode )
1204 if ( !curNode || curNode->head().isEmpty() )
1205 return false;
1206 if ( curNode->hasHeader("X-Mailman-Version") )
1207 return true;
1208 if ( curNode->hasHeader("X-Mailer") ) {
1209 KMime::Headers::Base *header = curNode->headerByType("X-Mailer");
1210 if ( header->asUnicodeString().contains("MAILMAN", Qt::CaseInsensitive ) )
1211 return true;
1213 return false;
1216 bool ObjectTreeParser::processMailmanMessage( KMime::Content* curNode ) {
1217 const QString str = QString::fromLatin1( curNode->decodedContent() );
1219 //###
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 ) {
1230 return false;
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) {
1244 return false;
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 );
1260 if ( -1 < thisEoL )
1261 thisDelim = thisEoL+1;
1262 else{
1263 thisEoL = str.indexOf("\n_____________", thisDelim, Qt::CaseInsensitive );
1264 if ( -1 < thisEoL )
1265 thisDelim = thisEoL+1;
1267 thisEoL = str.indexOf( '\n', thisDelim );
1268 if ( -1 < thisEoL )
1269 thisDelim = thisEoL+1;
1270 else
1271 thisDelim = thisDelim+1;
1272 //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] )
1273 // ++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 );
1281 if ( -1 < subPos ){
1282 subject = partStr.mid(subPos+subSearch.length());
1283 thisEoL = subject.indexOf('\n');
1284 if ( -1 < thisEoL )
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 );
1305 if ( -1 < thisEoL )
1306 thisDelim = thisEoL+1;
1308 else
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" );
1313 return true;
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 );
1326 return true;
1329 if ( !isFirstTextPart && attachmentStrategy()->defaultDisplay( curNode ) != AttachmentStrategy::Inline &&
1330 !showOnlyOneMimePart() )
1331 return false;
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();
1345 if ( bDrawFrame ) {
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" ) + "\">"
1359 + label + "</a>";
1360 else
1361 htmlStr += label;
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 );
1376 if ( bDrawFrame )
1377 htmlWriter()->queue( "</td></tr></table>" );
1379 return true;
1382 void ObjectTreeParser::stdChildHandling( KMime::Content * child ) {
1383 if ( !child )
1384 return;
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 )
1405 return false;
1407 htmlWriter()->queue( GlobalSettings::self()->toltecReplacementText() );
1408 htmlWriter()->queue( "<br/><br/><a href=\"kmail:showRawToltecMail\">" +
1409 i18n( "Show Raw Message" ) + "</a>" );
1410 return true;
1413 bool ObjectTreeParser::processMultiPartMixedSubtype( KMime::Content * node, ProcessResult & )
1415 if ( processToltecMail( node ) ) {
1416 return true;
1419 KMime::Content * child = MessageCore::NodeHelper::firstChild( node );
1420 if ( !child )
1421 return false;
1423 // normal treatment of the parts in the mp/mixed container
1424 stdChildHandling( child );
1425 return true;
1428 bool ObjectTreeParser::processMultiPartAlternativeSubtype( KMime::Content * node, ProcessResult & )
1430 KMime::Content * child = MessageCore::NodeHelper::firstChild( node );
1431 if ( !child )
1432 return false;
1434 KMime::Content* dataHtml = findType( child, "text/html", false, true );
1435 if ( !dataHtml ) {
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
1444 // here.
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()) ) {
1456 if ( dataPlain )
1457 mNodeHelper->setNodeProcessed( dataPlain, false);
1458 stdChildHandling( dataHtml );
1459 mSource->setHtmlMode( Util::MultipartHtml );
1460 return true;
1463 if ( !htmlWriter() || (!mSource->htmlMail() && dataPlain) ) {
1464 mNodeHelper->setNodeProcessed( dataHtml, false );
1465 stdChildHandling( dataPlain );
1466 mSource->setHtmlMode( Util::MultipartPlain );
1467 return true;
1470 stdChildHandling( child );
1471 return true;
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";
1488 if ( child )
1489 stdChildHandling( child );
1490 return 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 );
1503 return true;
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();
1522 if ( !protocol ) {
1523 mNodeHelper->setNodeProcessed( signature, true );
1524 stdChildHandling( signedData );
1525 return true;
1528 CryptoProtocolSaver saver( this, protocol );
1529 mNodeHelper->setSignatureState( node, KMMsgFullySigned);
1531 writeOpaqueOrMultipartSignedData( signedData, *signature,
1532 NodeHelper::fromAsString( node ) );
1533 return true;
1536 bool ObjectTreeParser::processMultiPartEncryptedSubtype( KMime::Content * node, ProcessResult & result )
1538 KMime::Content * child = MessageCore::NodeHelper::firstChild( node );
1539 if ( !child )
1540 return false;
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;
1550 return true;
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 );
1559 if ( data ) {
1560 useThisCryptProto = Kleo::CryptoBackendFactory::instance()->openpgp();
1562 if ( !data ) {
1563 data = findType( child, "application/pkcs7-mime", false, true );
1564 if ( data ) {
1565 useThisCryptProto = Kleo::CryptoBackendFactory::instance()->smime();
1569 ---------------------------------------------------------------------------------------------------------------
1572 if ( !data ) {
1573 stdChildHandling( child );
1574 return true;
1577 CryptoProtocolSaver cpws( this, useThisCryptProto );
1579 KMime::Content * dataChild = MessageCore::NodeHelper::firstChild( data );
1580 if ( dataChild ) {
1581 stdChildHandling( dataChild );
1582 return true;
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
1590 return true;
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 );
1606 } else {
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,
1615 decryptedData,
1616 signatureFound,
1617 signatures,
1618 true,
1619 passphraseError,
1620 actuallyEncrypted,
1621 decryptionStarted,
1622 messagePart );
1623 //kDebug() << "decrypted, signed?:" << signatureFound;
1625 if ( decryptionStarted ) {
1626 writeDecryptionInProgressBlock();
1627 return true;
1630 mNodeHelper->setNodeProcessed( data, false ); // Set the data node to done to prevent it from being processed
1632 // paint the frame
1633 if ( htmlWriter() ) {
1634 messagePart.isDecryptable = bOkDecrypt;
1635 messagePart.isEncrypted = true;
1636 messagePart.isSigned = false;
1637 htmlWriter()->queue( writeSigstatHeader( messagePart,
1638 cryptoProtocol(),
1639 NodeHelper::fromAsString( node ) ) );
1642 if ( bOkDecrypt ) {
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,
1656 *node,
1657 NodeHelper::fromAsString( node ),
1658 false,
1659 &decryptedData,
1660 signatures,
1661 false );
1662 mNodeHelper->setSignatureState( node, KMMsgFullySigned);
1663 //kDebug() << "setting FULLY SIGNED to:" << node;
1664 } else {
1665 decryptedData = KMime::CRLFtoLF( decryptedData ); //KMime works with LF only inside insertAndParseNewChildNode
1667 createAndParseTempNode( node, decryptedData.constData(),"encrypted data" );
1669 } else {
1670 mRawReplyString += decryptedData;
1671 if ( htmlWriter() ) {
1672 // print the error message that was returned in decryptedData
1673 // (utf8-encoded)
1674 htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) );
1679 mNodeHelper->setPartMetaData( node, messagePart );
1682 if ( htmlWriter() )
1683 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1684 return true;
1688 bool ObjectTreeParser::processMessageRfc822Subtype( KMime::Content * node, ProcessResult & )
1690 if ( htmlWriter() && !attachmentStrategy()->inlineNestedMessages() && !showOnlyOneMimePart() )
1691 return false;
1693 PartMetaData messagePart;
1694 messagePart.isEncrypted = false;
1695 messagePart.isSigned = false;
1696 messagePart.isEncapsulatedRfc822Message = true;
1698 KMime::Message::Ptr message = node->bodyAsMessage();
1699 if ( !message ) {
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,
1711 cryptoProtocol(),
1712 message->from()->asUnicodeString(),
1713 message.get() ) );
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 );
1734 return true;
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();
1747 return true;
1750 const Kleo::CryptoBackend::Protocol* oldUseThisCryptPlug = cryptoProtocol();
1751 if ( node->parent()
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();
1763 } else {
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,
1777 decryptedData,
1778 signatureFound,
1779 signatures,
1780 true,
1781 passphraseError,
1782 actuallyEncrypted,
1783 decryptionStarted,
1784 messagePart );
1786 if ( decryptionStarted ) {
1787 writeDecryptionInProgressBlock();
1788 return true;
1791 // paint the frame
1792 if ( htmlWriter() ) {
1793 messagePart.isDecryptable = bOkDecrypt;
1794 messagePart.isEncrypted = true;
1795 messagePart.isSigned = false;
1796 htmlWriter()->queue( writeSigstatHeader( messagePart,
1797 cryptoProtocol(),
1798 NodeHelper::fromAsString( node ) ) );
1801 if ( bOkDecrypt ) {
1802 // fixing the missing attachments bug #1090-b
1803 createAndParseTempNode( node, decryptedData.constData(), "encrypted data" );
1804 } else {
1805 mRawReplyString += decryptedData;
1806 if ( htmlWriter() ) {
1807 // print the error message that was returned in decryptedData
1808 // (utf8-encoded)
1809 htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) );
1813 if ( htmlWriter() )
1814 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1815 mNodeHelper->setPartMetaData( node, messagePart );
1817 return true;
1819 setCryptoProtocol( oldUseThisCryptPlug );
1820 return false;
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();
1832 return true;
1835 if ( node->head().isEmpty() )
1836 return false;
1838 const Kleo::CryptoBackend::Protocol * smimeCrypto = Kleo::CryptoBackendFactory::instance()->smime();
1839 if ( !smimeCrypto )
1840 return false;
1842 const QString smimeType = node->contentType()->parameter("smime-type").toLower();
1844 if ( smimeType == "certs-only" ) {
1845 result.setNeverDisplayInline( true );
1846 if ( !htmlWriter() )
1847 return false;
1849 if ( !GlobalSettings::self()->autoImportKeys() )
1850 return false;
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 );
1858 return true;
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.
1875 if ( !isSigned ) {
1876 if ( isEncrypted ) {
1877 ;//kDebug() << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data";
1878 } else {
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();
1893 isEncrypted = true;
1894 signTestNode = 0; // PENDING(marc) to be abs. sure, we'd need to have to look at the content
1895 } else {
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();
1902 return true;
1905 if ( bOkDecrypt ) {
1906 //kDebug() << "pkcs7 mime - encryption found - enveloped (encrypted) data !";
1907 isEncrypted = true;
1908 mNodeHelper->setEncryptionState( node, KMMsgFullyEncrypted );
1909 if( signatureFound )
1910 mNodeHelper->setSignatureState( node, KMMsgFullySigned );
1911 signTestNode = 0;
1912 // paint the frame
1913 messagePart.isDecryptable = true;
1914 if ( htmlWriter() )
1915 htmlWriter()->queue( writeSigstatHeader( messagePart,
1916 cryptoProtocol(),
1917 NodeHelper::fromAsString( node ) ) );
1918 createAndParseTempNode( node, decryptedData.constData(), "encrypted data" );
1919 if ( htmlWriter() )
1920 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1921 } else {
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 ) ) {
1927 isEncrypted = true;
1928 signTestNode = 0;
1931 if ( isEncrypted ) {
1932 //kDebug() << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !";
1933 // paint the frame
1934 messagePart.isDecryptable = false;
1935 if ( htmlWriter() ) {
1936 htmlWriter()->queue( writeSigstatHeader( messagePart,
1937 cryptoProtocol(),
1938 NodeHelper::fromAsString( node ) ) );
1939 assert( mSource->decryptMessage() ); // handled above
1940 writePartIcon( node );
1941 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1943 } else {
1944 //kDebug() << "pkcs7 mime - NO encryption found";
1948 if ( isEncrypted )
1949 mNodeHelper->setEncryptionState( node, KMMsgFullyEncrypted );
1950 mNodeHelper->setPartMetaData( node, messagePart );
1953 // We now try signature verification if necessarry.
1954 if ( signTestNode ) {
1955 if ( isSigned ) {
1956 ;//kDebug() << "pkcs7 mime == S/MIME TYPE: opaque signed data";
1957 } else {
1958 ;//kDebug() << "pkcs7 mime - type unknown - opaque signed data ?";
1961 bool sigFound = writeOpaqueOrMultipartSignedData( 0,
1962 *signTestNode,
1963 NodeHelper::fromAsString( node ),
1964 true,
1966 std::vector<GpgME::Signature>(),
1967 isEncrypted );
1968 if ( sigFound ) {
1969 if ( !isSigned ) {
1970 //kDebug() << "pkcs7 mime - signature found - opaque signed data !";
1971 isSigned = true;
1974 mNodeHelper->setSignatureState( signTestNode, KMMsgFullySigned );
1975 if ( signTestNode != node )
1976 mNodeHelper->setSignatureState( node, KMMsgFullySigned );
1977 } else {
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" );
1989 if ( !chiasmus )
1990 return false;
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." );
1996 return false;
1999 if ( listjob->exec() ) {
2000 errorText = i18n( "Chiasmus Backend Error" );
2001 return false;
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." );
2009 return false;
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 "
2016 "configuration." );
2017 return false;
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 ) {
2025 return false;
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>() );
2032 if ( !job ) {
2033 errorText = i18n( "Chiasmus backend does not offer the "
2034 "\"x-decrypt\" function. Please report this bug." );
2035 return false;
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." );
2043 return false;
2046 if ( job->exec() ) {
2047 errorText = i18n( "Chiasmus Decryption Error" );
2048 return false;
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." );
2056 return false;
2058 bodyDecoded = resultData.toByteArray();
2059 return true;
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 );
2068 return true;
2071 QByteArray decryptedBody;
2072 QString errorText;
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;
2080 if ( htmlWriter() )
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 );
2090 if ( htmlWriter() )
2091 htmlWriter()->queue( writeSigstatFooter( messagePart ) );
2092 mNodeHelper->setPartMetaData( curNode, messagePart );
2093 return true;
2096 void ObjectTreeParser::writeBodyString( const QByteArray & bodyString,
2097 const QString & fromAddress,
2098 const QTextCodec * codec,
2099 ProcessResult & result,
2100 bool decorate )
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() )
2109 return;
2111 assert( codec );
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 )
2123 return;
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 )
2133 comment.clear();
2135 QString fileName = mNodeHelper->writeNodeToTempFile( msgPart );
2136 QString href = mNodeHelper->asHREF( msgPart, "body" );
2138 QString iconName;
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>"
2144 "</div>"
2145 "<div><a href=\"" + href + "\">" + label + "</a>"
2146 "</div>"
2147 "<div>" + comment + "</div><br/>" );
2148 } else {
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 +
2157 "</a></div>"
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,
2167 int status_code,
2168 GpgME::Signature::Summary summary,
2169 int& frameColor,
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;
2176 QString result;
2177 if( cryptProto ) {
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");
2184 break;
2185 case 1: // GPGME_SIG_STAT_GOOD
2186 result = i18n("Good signature");
2187 break;
2188 case 2: // GPGME_SIG_STAT_BAD
2189 result = i18n("<b>Bad</b> signature");
2190 break;
2191 case 3: // GPGME_SIG_STAT_NOKEY
2192 result = i18n("No public key to verify the signature");
2193 break;
2194 case 4: // GPGME_SIG_STAT_NOSIG
2195 result = i18n("No signature found");
2196 break;
2197 case 5: // GPGME_SIG_STAT_ERROR
2198 result = i18n("Error verifying the signature");
2199 break;
2200 case 6: // GPGME_SIG_STAT_DIFF
2201 result = i18n("Different results for signatures");
2202 break;
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");
2206 break;
2207 case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
2208 return i18n("One of the certificate's keys is expired");
2209 break;
2211 default:
2212 result = ""; // do *not* return a default text here !
2213 break;
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;
2224 return result;
2227 if( summary & GpgME::Signature::Valid ) {
2228 result = i18n("Good signature.");
2229 // Note:
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;
2239 return result;
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;
2246 QString result2;
2247 if( summary & GpgME::Signature::KeyExpired ){
2248 // still is green!
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.");
2256 // test for yellow:
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;
2285 // test for red:
2286 if( summary & GpgME::Signature::KeyRevoked ){
2287 // this is red!
2288 result2 += i18n("One key has been revoked.");
2289 frameColor = SIG_FRAME_COL_RED;
2291 if( summary & GpgME::Signature::Red ) {
2292 if( result2.isEmpty() )
2293 // Note:
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;
2308 else
2309 result = "";
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.");
2315 } else
2316 result = "";
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 )) {
2331 return result;
2335 static QString writeSimpleSigstatHeader( const PartMetaData &block )
2337 QString html;
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" ) {
2345 QString addr;
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." );
2353 } else {
2354 html += i18n( "Signed by <a href=\"mailto:%1\">%2</a>.", addr, name );
2356 } else {
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>";
2364 return html;
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)";
2379 return QString();
2380 } else if ( code == GPG_ERR_NO_DATA ) {
2381 kDebug() << "not showing link (not available)";
2382 return i18n("No Audit Log available");
2383 } else {
2384 return i18n("Error Retrieving Audit Log: %1", QString::fromLocal8Bit( err.asString() ) );
2388 if ( !auditLog.isEmpty() ) {
2389 KUrl url;
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>";
2397 return QString();
2400 static QString endVerboseSigstatHeader( const PartMetaData & pmd )
2402 QString html;
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>";
2410 return html;
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 + "\">";
2429 if( node ) {
2430 htmlStr += "<a href=\"" + mNodeHelper->asHREF( node, "body" ) + "\">"
2431 + i18n("Encapsulated message") + "</a>";
2432 } else {
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");
2445 } else {
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;
2470 bool showKeyInfos;
2471 bool onlyShowKeyURL = false;
2472 bool cannotCheckSignature = true;
2473 QString statusStr = sigStatusToString( cryptProto,
2474 block.status_code,
2475 block.sigSummary,
2476 frameColor,
2477 showKeyInfos );
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;
2488 break;
2489 case SIG_FRAME_COL_YELLOW:
2490 cannotCheckSignature = true;
2491 break;
2492 case SIG_FRAME_COL_GREEN:
2493 cannotCheckSignature = false;
2494 break;
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;
2503 if( isSMIME )
2504 startKeyHREF =
2505 QString("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
2506 .arg( cryptProto->displayName(),
2507 cryptProto->name(),
2508 QString::fromLatin1( block.keyId ) );
2509 QString keyWithWithoutURL
2510 // FIXME: Kleopatra misses a -query option, so disable this for now.
2511 = /*isSMIME
2512 ? QString("%1%2</a>")
2513 .arg( startKeyHREF,
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
2523 // some reason.
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;
2538 break;
2539 case SIG_FRAME_COL_YELLOW:
2540 if( block.technicalProblem )
2541 block.signClass = "signWarn";
2542 else
2543 block.signClass = "signOkKeyBad";//"signCMSYellow";
2544 break;
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");
2554 else
2555 certificate = startKeyHREF + i18n("certificate") + "</a>";
2556 if( !blockAddrs.empty() ){
2557 if( !blockAddrs.contains( msgFrom, Qt::CaseInsensitive ) ) {
2558 greenCaseWarning =
2559 "<u>" +
2560 i18nc("Start of warning message."
2561 ,"Warning:") +
2562 "</u> " +
2563 i18n("Sender's mail address is not stored "
2564 "in the %1 used for signing.", certificate) +
2565 "<br />" +
2566 i18n("sender: ") +
2567 msgFrom +
2568 "<br />" +
2569 i18n("stored: ");
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:
2574 bool bStart = true;
2575 for(QStringList::ConstIterator it = blockAddrs.constBegin();
2576 it != blockAddrs.constEnd(); ++it ){
2577 if( !bStart )
2578 greenCaseWarning.append(", <br />&nbsp; &nbsp;");
2579 bStart = false;
2580 greenCaseWarning.append( KPIMUtils::extractEmailAddress(*it) );
2583 } else {
2584 greenCaseWarning =
2585 "<u>" +
2586 i18nc("Start of warning message.","Warning:") +
2587 "</u> " +
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.",
2590 certificate,
2591 msgFrom);
2593 if( !greenCaseWarning.isEmpty() ) {
2594 if( !statusStr.isEmpty() )
2595 statusStr.append("<br />&nbsp;<br />");
2596 statusStr.append( greenCaseWarning );
2598 break;
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 "
2613 "signature. %1",
2614 keyWithWithoutURL );
2616 else {
2618 if (block.signer.isEmpty())
2619 signer = "";
2620 else {
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." );
2631 else
2632 htmlStr += i18n( "Message was signed by %1.",
2633 signer );
2634 } else {
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 );
2641 else
2642 htmlStr += i18n( "Message was signed on %1 with key %2.",
2643 KGlobal::locale()->formatDateTime( created ),
2644 keyWithWithoutURL );
2646 else {
2647 if( onlyShowKeyURL )
2648 htmlStr += i18n( "Message was signed with key %1.",
2649 keyWithWithoutURL );
2650 else
2651 htmlStr += i18n( "Message was signed by %3 on %1 with key %2",
2652 KGlobal::locale()->formatDateTime( created ),
2653 keyWithWithoutURL,
2654 signer );
2657 else {
2658 if( signer.isEmpty() || onlyShowKeyURL )
2659 htmlStr += i18n( "Message was signed with key %1.",
2660 keyWithWithoutURL );
2661 else
2662 htmlStr += i18n( "Message was signed by %2 with key %1.",
2663 keyWithWithoutURL,
2664 signer );
2668 htmlStr += "<br />";
2669 if( !statusStr.isEmpty() ) {
2670 htmlStr += "&nbsp;<br />";
2671 htmlStr += i18n( "Status: " );
2672 htmlStr += statusStr;
2674 } else {
2675 htmlStr += statusStr;
2677 frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
2678 htmlStr += endVerboseSigstatHeader( block ) + frame;
2679 simpleHtmlStr += frame;
2681 } else {
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;
2696 else {
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 );
2703 else
2704 htmlStr += i18n( "Message was signed with unknown key %1.",
2705 keyWithWithoutURL );
2707 else
2708 htmlStr += i18n( "Message was signed with unknown key." );
2709 htmlStr += "<br />";
2710 htmlStr += i18n( "The validity of the signature cannot be "
2711 "verified." );
2712 if( !statusStr.isEmpty() ) {
2713 htmlStr += "<br />";
2714 htmlStr += i18n( "Status: " );
2715 htmlStr += "<i>";
2716 htmlStr += statusStr;
2717 htmlStr += "</i>";
2720 frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
2721 htmlStr += endVerboseSigstatHeader( block ) + frame;
2722 simpleHtmlStr += frame;
2724 else
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";
2733 else
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).",
2743 keyWithWithoutURL,
2744 signer );
2745 else
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." );
2754 break;
2755 case Kpgp::KPGP_VALIDITY_MARGINAL:
2756 htmlStr += i18n( "The signature is valid and the key is "
2757 "marginally trusted." );
2758 break;
2759 case Kpgp::KPGP_VALIDITY_FULL:
2760 htmlStr += i18n( "The signature is valid and the key is "
2761 "fully trusted." );
2762 break;
2763 case Kpgp::KPGP_VALIDITY_ULTIMATE:
2764 htmlStr += i18n( "The signature is valid and the key is "
2765 "ultimately trusted." );
2766 break;
2767 default:
2768 htmlStr += i18n( "The signature is valid, but the key is "
2769 "untrusted." );
2771 frame = "</td></tr>"
2772 "<tr class=\"" + block.signClass + "B\"><td>";
2773 htmlStr += endVerboseSigstatHeader( block ) + frame;
2774 simpleHtmlStr += frame;
2776 else
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).",
2787 keyWithWithoutURL,
2788 signer );
2789 else
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() )
2803 return htmlStr;
2804 return simpleHtmlStr;
2807 QString ObjectTreeParser::writeSigstatFooter( PartMetaData& block )
2809 QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
2811 QString htmlStr;
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>";
2833 return htmlStr;
2837 //-----------------------------------------------------------------------------
2839 void ObjectTreeParser::writeAttachmentMarkHeader( KMime::Content *node )
2841 if ( !htmlWriter() )
2842 return;
2844 htmlWriter()->queue( QString( "<div id=\"attachmentDiv%1\">\n" ).arg( node->index().toString() ) );
2847 //-----------------------------------------------------------------------------
2849 void ObjectTreeParser::writeAttachmentMarkFooter()
2851 if ( !htmlWriter() )
2852 return;
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,
2873 bool decorate )
2875 bool goodSignature = false;
2876 Kpgp::Module* pgp = Kpgp::Module::getKpgp();
2877 assert(pgp != 0);
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;
2893 QString signer;
2894 QByteArray keyId;
2895 QString decryptionError;
2896 Kpgp::Validity keyTrust = Kpgp::KPGP_VALIDITY_FULL;
2898 QList<Kpgp::Block>::iterator pbit = pgpBlocks.begin();
2899 QListIterator<QByteArray> npbit( nonPgpBlocks );
2900 QString htmlStr;
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 ) {
2913 if( *c != '\n' ) {
2914 fullySignedOrEncrypted = false;
2915 break;
2919 else {
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();
2944 else
2946 // try to verify this OpenPGP block
2947 block.verify();
2950 isSigned = block.isSigned();
2951 if( 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 );
2962 if ( key ) {
2963 // Use the user ID from the key because this one
2964 // is charset safe.
2965 signer = key->primaryUserID();
2968 else
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 );
2975 if( isSigned )
2976 inlineSignatureState = KMMsgPartiallySigned;
2977 if( isEncrypted )
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 );
2998 else {
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() ),
3006 decorate );
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 );
3027 else
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;
3040 QString htmlStr;
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--;
3060 beg = pos;
3062 int currQuoteLevel = -2; // -2 == no previous lines
3063 bool curHidden = false; // no hide any block
3065 if ( GlobalSettings::self()->showExpandQuotesMark() )
3067 // Cache Icons
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 ));
3077 while (beg<length)
3079 QString line;
3081 /* search next occurrence of '\n' */
3082 pos = s.indexOf('\n', beg, Qt::CaseInsensitive);
3083 if (pos == (unsigned int)(-1))
3084 pos = length;
3086 line = s.mid(beg,pos-beg);
3087 beg = pos+1;
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()) {
3094 case '>':
3095 case '|':
3096 actQuoteLevel++;
3097 break;
3098 case ' ': // spaces and tabs are allowed between the quote markers
3099 case '\t':
3100 case '\r':
3101 break;
3102 default: // stop quoting depth calculation
3103 p = line.length();
3104 break;
3106 } /* for() */
3108 bool actHidden = false;
3110 // This quoted line needs be hidden
3111 if (GlobalSettings::self()->showExpandQuotesMark() && mSource->levelQuote() >= 0
3112 && mSource->levelQuote() <= ( actQuoteLevel ) )
3113 actHidden = true;
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;
3126 } else {
3127 if ( GlobalSettings::self()->showExpandQuotesMark() ) {
3128 if ( actHidden ) {
3129 //only show the QuoteMark when is the first line of the level hidden
3130 if ( !curHidden ) {
3131 //Expand all quotes
3132 htmlStr += "<div class=\"quotelevelmark\" >" ;
3133 htmlStr += QString( "<a href=\"kmail:levelquote?%1 \">"
3134 "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
3135 .arg(-1)
3136 .arg( mExpandIcon );
3137 htmlStr += "</div><br/>";
3138 htmlStr += quoteEnd;
3140 } else {
3141 htmlStr += "<div class=\"quotelevelmark\" >" ;
3142 htmlStr += QString( "<a href=\"kmail:levelquote?%1 \">"
3143 "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
3144 .arg(actQuoteLevel)
3145 .arg( mCollapseIcon);
3146 htmlStr += "</div>";
3147 if ( actQuoteLevel < 3 ) {
3148 htmlStr += quoteFontTag[actQuoteLevel];
3149 } else {
3150 htmlStr += deepQuoteFontTag[actQuoteLevel%3];
3153 } else {
3154 if ( actQuoteLevel < 3 ) {
3155 htmlStr += quoteFontTag[actQuoteLevel];
3156 } else {
3157 htmlStr += deepQuoteFontTag[actQuoteLevel%3];
3161 currQuoteLevel = actQuoteLevel;
3163 curHidden = actHidden;
3166 if ( !actHidden )
3168 // don't write empty <div ...></div> blocks (they have zero height)
3169 // ignore ^M DOS linebreaks
3170 if( !line.remove( '\015' ).isEmpty() )
3172 if ( startNewPara )
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 );
3179 else
3181 htmlStr += "<br/>";
3182 // after an empty line, always start a new paragraph
3183 startNewPara = true;
3186 } /* while() */
3188 /* really finish the last quotelevel */
3189 if (currQuoteLevel == -1) {
3190 htmlStr.append( normalEndTag );
3191 } else {
3192 htmlStr.append( quoteEnd );
3195 //kDebug() << "========================================\n"
3196 // << htmlStr
3197 // << "\n======================================\n";
3198 return htmlStr;
3203 const QTextCodec * ObjectTreeParser::codecFor( KMime::Content * node ) const
3205 assert( node );
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 ) {
3227 return false;
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 ) {
3236 return true;
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 ) {
3243 nextEnd = length;
3245 QString nextLine = s.mid( nextStart, nextEnd - nextStart );
3246 length = nextLine.length();
3247 // search for first word in next line
3248 unsigned int wordStart;
3249 bool found = false;
3250 for ( wordStart = 0; !found && wordStart < length; wordStart++ ) {
3251 switch ( nextLine[wordStart].toLatin1() ) {
3252 case '>':
3253 case '|':
3254 case ' ': // spaces, tabs and quote markers don't count
3255 case '\t':
3256 case '\r':
3257 break;
3258 default:
3259 found = true;
3260 break;
3262 } /* for() */
3264 if ( !found ) {
3265 // next line is essentially empty, it seems -- empty lines are
3266 // para separators
3267 return true;
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
3272 //refactoring.
3273 int wordEnd = nextLine.indexOf( ' ', wordStart );
3274 if ( wordEnd == (-1) ) {
3275 wordEnd = length;
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
3281 // meaningful
3282 return prevLineLength + wordLength + 1 < WRAP_COL;
3285 #ifdef MARCS_DEBUG
3286 void ObjectTreeParser::dumpToFile( const char * filename, const char * start,
3287 size_t len ) {
3288 assert( filename );
3290 QFile f( filename );
3291 if ( f.open( QIODevice::WriteOnly ) ) {
3292 if ( start ) {
3293 QDataStream ds( &f );
3294 ds.writeRawData( start, len );
3296 f.close(); // If data is 0 we just create a zero length file.
3299 #endif // !NDEBUG
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() ) ) )
3306 return content;
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 );
3315 return 0;
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() ) )
3323 return content;
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 );
3333 return 0;
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 )
3342 return content;
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 );
3348 if ( next && wide )
3349 return findTypeNot( next, mediaType, subType, deep, wide );
3350 return 0;