kmail

objecttreeparser.cpp

Go to the documentation of this file.
00001 /*  -*- mode: C++; c-file-style: "gnu" -*-
00002     objecttreeparser.cpp
00003 
00004     This file is part of KMail, the KDE mail client.
00005     Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
00006     Copyright (c) 2003      Marc Mutz <mutz@kde.org>
00007 
00008     KMail is free software; you can redistribute it and/or modify it
00009     under the terms of the GNU General Public License, version 2, as
00010     published by the Free Software Foundation.
00011 
00012     KMail is distributed in the hope that it will be useful, but
00013     WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015     General Public License for more details.
00016 
00017     You should have received a copy of the GNU General Public License
00018     along with this program; if not, write to the Free Software
00019     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00020 
00021     In addition, as a special exception, the copyright holders give
00022     permission to link the code of this program with any edition of
00023     the Qt library by Trolltech AS, Norway (or with modified versions
00024     of Qt that use the same license as Qt), and distribute linked
00025     combinations including the two.  You must obey the GNU General
00026     Public License in all respects for all of the code used other than
00027     Qt.  If you modify this file, you may extend this exception to
00028     your version of the file, but you are not obligated to do so.  If
00029     you do not wish to do so, delete this exception statement from
00030     your version.
00031 */
00032 
00033 #include <config.h>
00034 
00035 // my header file
00036 #include "objecttreeparser.h"
00037 
00038 // other KMail headers
00039 #include "kmkernel.h"
00040 #include "kmreaderwin.h"
00041 #include "partNode.h"
00042 #include <libkdepim/kfileio.h>
00043 #include <libemailfunctions/email.h>
00044 #include "partmetadata.h"
00045 #include "attachmentstrategy.h"
00046 #include "interfaces/htmlwriter.h"
00047 #include "htmlstatusbar.h"
00048 #include "csshelper.h"
00049 #include "bodypartformatter.h"
00050 #include "bodypartformatterfactory.h"
00051 #include "partnodebodypart.h"
00052 #include "interfaces/bodypartformatter.h"
00053 #include "globalsettings.h"
00054 #include "util.h"
00055 
00056 // other module headers
00057 #include <mimelib/enum.h>
00058 #include <mimelib/bodypart.h>
00059 #include <mimelib/string.h>
00060 #include <mimelib/text.h>
00061 
00062 #include <kleo/specialjob.h>
00063 #include <kleo/cryptobackendfactory.h>
00064 #include <kleo/decryptverifyjob.h>
00065 #include <kleo/verifydetachedjob.h>
00066 #include <kleo/verifyopaquejob.h>
00067 #include <kleo/keylistjob.h>
00068 #include <kleo/importjob.h>
00069 #include <kleo/dn.h>
00070 
00071 #include <gpgmepp/importresult.h>
00072 #include <gpgmepp/decryptionresult.h>
00073 #include <gpgmepp/key.h>
00074 #include <gpgmepp/keylistresult.h>
00075 #include <gpgme.h>
00076 
00077 #include <kpgpblock.h>
00078 #include <kpgp.h>
00079 #include <linklocator.h>
00080 
00081 #include <ktnef/ktnefparser.h>
00082 #include <ktnef/ktnefmessage.h>
00083 #include <ktnef/ktnefattach.h>
00084 
00085 // other KDE headers
00086 #include <kdebug.h>
00087 #include <klocale.h>
00088 #include <kmimetype.h>
00089 #include <kglobal.h>
00090 #include <khtml_part.h>
00091 #include <ktempfile.h>
00092 #include <kstandarddirs.h>
00093 #include <kapplication.h>
00094 #include <kmessagebox.h>
00095 #include <kiconloader.h>
00096 #include <kmdcodec.h>
00097 
00098 // other Qt headers
00099 #include <qtextcodec.h>
00100 #include <qdir.h>
00101 #include <qfile.h>
00102 #include <qapplication.h>
00103 #include <kstyle.h>
00104 #include <qbuffer.h>
00105 #include <qpixmap.h>
00106 #include <qpainter.h>
00107 #include <qregexp.h>
00108 
00109 // other headers
00110 #include <memory>
00111 #include <sys/stat.h>
00112 #include <sys/types.h>
00113 #include <unistd.h>
00114 #include "chiasmuskeyselector.h"
00115 
00116 namespace KMail {
00117 
00118   // A small class that eases temporary CryptPlugWrapper changes:
00119   class ObjectTreeParser::CryptoProtocolSaver {
00120     ObjectTreeParser * otp;
00121     const Kleo::CryptoBackend::Protocol * protocol;
00122   public:
00123     CryptoProtocolSaver( ObjectTreeParser * _otp, const Kleo::CryptoBackend::Protocol* _w )
00124       : otp( _otp ), protocol( _otp ? _otp->cryptoProtocol() : 0 )
00125     {
00126       if ( otp )
00127         otp->setCryptoProtocol( _w );
00128     }
00129 
00130     ~CryptoProtocolSaver() {
00131       if ( otp )
00132         otp->setCryptoProtocol( protocol );
00133     }
00134   };
00135 
00136 
00137   ObjectTreeParser::ObjectTreeParser( KMReaderWin * reader, const Kleo::CryptoBackend::Protocol * protocol,
00138                                       bool showOnlyOneMimePart, bool keepEncryptions,
00139                                       bool includeSignatures,
00140                                       const AttachmentStrategy * strategy,
00141                                       HtmlWriter * htmlWriter,
00142                                       CSSHelper * cssHelper )
00143     : mReader( reader ),
00144       mCryptoProtocol( protocol ),
00145       mShowOnlyOneMimePart( showOnlyOneMimePart ),
00146       mKeepEncryptions( keepEncryptions ),
00147       mIncludeSignatures( includeSignatures ),
00148       mAttachmentStrategy( strategy ),
00149       mHtmlWriter( htmlWriter ),
00150       mCSSHelper( cssHelper )
00151   {
00152     if ( !attachmentStrategy() )
00153       mAttachmentStrategy = reader ? reader->attachmentStrategy()
00154                                    : AttachmentStrategy::smart();
00155     if ( reader && !this->htmlWriter() )
00156       mHtmlWriter = reader->htmlWriter();
00157     if ( reader && !this->cssHelper() )
00158       mCSSHelper = reader->mCSSHelper;
00159   }
00160 
00161   ObjectTreeParser::ObjectTreeParser( const ObjectTreeParser & other )
00162     : mReader( other.mReader ),
00163       mCryptoProtocol( other.cryptoProtocol() ),
00164       mShowOnlyOneMimePart( other.showOnlyOneMimePart() ),
00165       mKeepEncryptions( other.keepEncryptions() ),
00166       mIncludeSignatures( other.includeSignatures() ),
00167       mAttachmentStrategy( other.attachmentStrategy() ),
00168       mHtmlWriter( other.htmlWriter() ),
00169       mCSSHelper( other.cssHelper() )
00170   {
00171 
00172   }
00173 
00174   ObjectTreeParser::~ObjectTreeParser() {}
00175 
00176   void ObjectTreeParser::insertAndParseNewChildNode( partNode& startNode,
00177                                                      const char* content,
00178                                                      const char* cntDesc,
00179                                                      bool append )
00180   {
00181     DwBodyPart* myBody = new DwBodyPart( DwString( content ), 0 );
00182     myBody->Parse();
00183 
00184     if ( ( !myBody->Body().FirstBodyPart() ||
00185            myBody->Body().AsString().length() == 0 ) &&
00186          startNode.dwPart() &&
00187          startNode.dwPart()->Body().Message() &&
00188          startNode.dwPart()->Body().Message()->Body().FirstBodyPart() )
00189     {
00190       // if encapsulated imap messages are loaded the content-string is not complete
00191       // so we need to keep the child dwparts
00192       myBody = new DwBodyPart( *(startNode.dwPart()->Body().Message()) );
00193     }
00194 
00195     if ( myBody->hasHeaders() ) {
00196       DwText& desc = myBody->Headers().ContentDescription();
00197       desc.FromString( cntDesc );
00198       desc.SetModified();
00199       myBody->Headers().Parse();
00200     }
00201 
00202     partNode* parentNode = &startNode;
00203     partNode* newNode = new partNode(false, myBody);
00204     if ( append && parentNode->firstChild() ) {
00205       parentNode = parentNode->firstChild();
00206       while( parentNode->nextSibling() )
00207         parentNode = parentNode->nextSibling();
00208       parentNode->setNext( newNode );
00209     } else
00210       parentNode->setFirstChild( newNode );
00211 
00212     newNode->buildObjectTree( false );
00213 
00214     if ( startNode.mimePartTreeItem() ) {
00215       kdDebug(5006) << "\n     ----->  Inserting items into MimePartTree\n" << endl;
00216       newNode->fillMimePartTree( startNode.mimePartTreeItem(), 0,
00217                                  QString::null, QString::null, QString::null, 0,
00218                                  append );
00219       kdDebug(5006) << "\n     <-----  Finished inserting items into MimePartTree\n" << endl;
00220     } else {
00221       kdDebug(5006) << "\n     ------  Sorry, node.mimePartTreeItem() returns ZERO so"
00222                     << "\n                    we cannot insert new lines into MimePartTree. :-(\n" << endl;
00223     }
00224     kdDebug(5006) << "\n     ----->  Now parsing the MimePartTree\n" << endl;
00225     ObjectTreeParser otp( mReader, cryptoProtocol() );
00226     otp.parseObjectTree( newNode );
00227     mRawReplyString += otp.rawReplyString();
00228     mTextualContent += otp.textualContent();
00229     if ( !otp.textualContentCharset().isEmpty() )
00230       mTextualContentCharset = otp.textualContentCharset();
00231     kdDebug(5006) << "\n     <-----  Finished parsing the MimePartTree in insertAndParseNewChildNode()\n" << endl;
00232   }
00233 
00234 
00235 //-----------------------------------------------------------------------------
00236 
00237   void ObjectTreeParser::parseObjectTree( partNode * node ) {
00238     kdDebug(5006) << "ObjectTreeParser::parseObjectTree( "
00239                   << (node ? "node OK, " : "no node, ")
00240                   << "showOnlyOneMimePart: " << (showOnlyOneMimePart() ? "TRUE" : "FALSE")
00241                   << " )" << endl;
00242 
00243     if ( !node )
00244       return;
00245 
00246     // reset "processed" flags for...
00247     if ( showOnlyOneMimePart() ) {
00248       // ... this node and all descendants
00249       node->setProcessed( false, false );
00250       if ( partNode * child = node->firstChild() )
00251         child->setProcessed( false, true );
00252     } else if ( mReader && !node->parentNode() ) {
00253       // ...this node and all it's siblings and descendants
00254       node->setProcessed( false, true );
00255     }
00256 
00257     for ( ; node ; node = node->nextSibling() ) {
00258       if ( node->processed() )
00259         continue;
00260 
00261       ProcessResult processResult;
00262 
00263       if ( mReader )
00264         htmlWriter()->queue( QString::fromLatin1("<a name=\"att%1\"/>").arg( node->nodeId() ) );
00265       if ( const Interface::BodyPartFormatter * formatter
00266            = BodyPartFormatterFactory::instance()->createFor( node->typeString(), node->subTypeString() ) ) {
00267         PartNodeBodyPart part( *node, codecFor( node ) );
00268         // Set the default display strategy for this body part relying on the
00269         // identity of KMail::Interface::BodyPart::Display and AttachmentStrategy::Display
00270         part.setDefaultDisplay( (KMail::Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay( node ) );
00271         const Interface::BodyPartFormatter::Result result = formatter->format( &part, htmlWriter() );
00272         if ( mReader && node->bodyPartMemento() )
00273           if ( Interface::Observable * obs = node->bodyPartMemento()->asObservable() )
00274             obs->attach( mReader );
00275         switch ( result ) {
00276         case Interface::BodyPartFormatter::AsIcon:
00277           processResult.setNeverDisplayInline( true );
00278           // fall through:
00279         case Interface::BodyPartFormatter::Failed:
00280           defaultHandling( node, processResult );
00281           break;
00282         case Interface::BodyPartFormatter::Ok:
00283         case Interface::BodyPartFormatter::NeedContent:
00284           // FIXME: incomplete content handling
00285           ;
00286         }
00287       } else {
00288         const BodyPartFormatter * bpf
00289           = BodyPartFormatter::createFor( node->type(), node->subType() );
00290         kdFatal( !bpf, 5006 ) << "THIS SHOULD NO LONGER HAPPEN ("
00291                               << node->typeString() << '/' << node->subTypeString()
00292                               << ')' << endl;
00293 
00294         if ( bpf && !bpf->process( this, node, processResult ) )
00295           defaultHandling( node, processResult );
00296       }
00297       node->setProcessed( true, false );
00298 
00299       // adjust signed/encrypted flags if inline PGP was found
00300       processResult.adjustCryptoStatesOfNode( node );
00301 
00302       if ( showOnlyOneMimePart() )
00303         break;
00304     }
00305   }
00306 
00307   void ObjectTreeParser::defaultHandling( partNode * node, ProcessResult & result ) {
00308     // ### (mmutz) default handling should go into the respective
00309     // ### bodypartformatters.
00310     if ( !mReader )
00311       return;
00312     if ( attachmentStrategy() == AttachmentStrategy::hidden() &&
00313          !showOnlyOneMimePart() &&
00314          node->parentNode() /* message is not an attachment */ )
00315       return;
00316 
00317     bool asIcon = true;
00318     if ( showOnlyOneMimePart() )
00319       // ### (mmutz) this is wrong! If I click on an image part, I
00320       // want the equivalent of "view...", except for the extra
00321       // window!
00322       asIcon = !node->hasContentDispositionInline();
00323     else if ( !result.neverDisplayInline() )
00324       if ( const AttachmentStrategy * as = attachmentStrategy() )
00325         asIcon = as->defaultDisplay( node ) == AttachmentStrategy::AsIcon;
00326     // neither image nor text -> show as icon
00327     if ( !result.isImage()
00328          && node->type() != DwMime::kTypeText )
00329       asIcon = true;
00330     // if the image is not complete do not try to show it inline
00331     if ( result.isImage() && !node->msgPart().isComplete() )
00332       asIcon = true;
00333     if ( asIcon ) {
00334       if ( attachmentStrategy() != AttachmentStrategy::hidden()
00335            || showOnlyOneMimePart() )
00336         writePartIcon( &node->msgPart(), node->nodeId() );
00337     } else if ( result.isImage() )
00338       writePartIcon( &node->msgPart(), node->nodeId(), true );
00339     else
00340       writeBodyString( node->msgPart().bodyDecoded(),
00341                        node->trueFromAddress(),
00342                        codecFor( node ), result, false );
00343     // end of ###
00344   }
00345 
00346   void ProcessResult::adjustCryptoStatesOfNode( partNode * node ) const {
00347     if ( ( inlineSignatureState()  != KMMsgNotSigned ) ||
00348          ( inlineEncryptionState() != KMMsgNotEncrypted ) ) {
00349       node->setSignatureState( inlineSignatureState() );
00350       node->setEncryptionState( inlineEncryptionState() );
00351     }
00352   }
00353 
00357 
00358   static int signatureToStatus( const GpgME::Signature &sig )
00359   {
00360     switch ( sig.status().code() ) {
00361       case GPG_ERR_NO_ERROR:
00362         return GPGME_SIG_STAT_GOOD;
00363       case GPG_ERR_BAD_SIGNATURE:
00364         return GPGME_SIG_STAT_BAD;
00365       case GPG_ERR_NO_PUBKEY:
00366         return GPGME_SIG_STAT_NOKEY;
00367       case GPG_ERR_NO_DATA:
00368         return GPGME_SIG_STAT_NOSIG;
00369       case GPG_ERR_SIG_EXPIRED:
00370         return GPGME_SIG_STAT_GOOD_EXP;
00371       case GPG_ERR_KEY_EXPIRED:
00372         return GPGME_SIG_STAT_GOOD_EXPKEY;
00373       default:
00374         return GPGME_SIG_STAT_ERROR;
00375     }
00376   }
00377 
00378   bool ObjectTreeParser::writeOpaqueOrMultipartSignedData( partNode* data,
00379                                                       partNode& sign,
00380                                                       const QString& fromAddress,
00381                                                       bool doCheck,
00382                                                       QCString* cleartextData,
00383                                                       std::vector<GpgME::Signature> paramSignatures,
00384                                                       bool hideErrors )
00385   {
00386     bool bIsOpaqueSigned = false;
00387     enum { NO_PLUGIN, NOT_INITIALIZED, CANT_VERIFY_SIGNATURES }
00388       cryptPlugError = NO_PLUGIN;
00389 
00390     const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
00391 
00392     QString cryptPlugLibName;
00393     QString cryptPlugDisplayName;
00394     if ( cryptProto ) {
00395       cryptPlugLibName = cryptProto->name();
00396       cryptPlugDisplayName = cryptProto->displayName();
00397     }
00398 
00399 #ifndef NDEBUG
00400     if ( !doCheck )
00401       kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: showing OpenPGP (Encrypted+Signed) data" << endl;
00402     else
00403       if ( data )
00404         kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Multipart Signed data" << endl;
00405       else
00406         kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Opaque Signed data" << endl;
00407 #endif
00408 
00409     if ( doCheck && cryptProto ) {
00410       kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: going to call CRYPTPLUG "
00411                     << cryptPlugLibName << endl;
00412     }
00413 
00414     QCString cleartext;
00415     QByteArray signaturetext;
00416 
00417     if ( doCheck && cryptProto ) {
00418       if ( data ) {
00419         cleartext = KMail::Util::CString( data->dwPart()->AsString() );
00420 
00421         dumpToFile( "dat_01_reader_signedtext_before_canonicalization",
00422                     cleartext.data(), cleartext.length() );
00423 
00424         // replace simple LFs by CRLSs
00425         // according to RfC 2633, 3.1.1 Canonicalization
00426         kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
00427         cleartext = Util::lf2crlf( cleartext );
00428         kdDebug(5006) << "                                                       done." << endl;
00429       }
00430 
00431       dumpToFile( "dat_02_reader_signedtext_after_canonicalization",
00432                   cleartext.data(), cleartext.length() );
00433 
00434       signaturetext = sign.msgPart().bodyDecodedBinary();
00435       dumpToFile( "dat_03_reader.sig", signaturetext.data(),
00436                   signaturetext.size() );
00437     }
00438 
00439     std::vector<GpgME::Signature> signatures;
00440     if ( doCheck )
00441       signatures = paramSignatures;
00442 
00443     PartMetaData messagePart;
00444     messagePart.isSigned = true;
00445     messagePart.technicalProblem = ( cryptProto == 0 );
00446     messagePart.isGoodSignature = false;
00447     messagePart.isEncrypted = false;
00448     messagePart.isDecryptable = false;
00449     messagePart.keyTrust = Kpgp::KPGP_VALIDITY_UNKNOWN;
00450     messagePart.status = i18n("Wrong Crypto Plug-In.");
00451     messagePart.status_code = GPGME_SIG_STAT_NONE;
00452 
00453     if ( doCheck && cryptProto ) {
00454       GpgME::VerificationResult result;
00455       if ( data ) { // detached
00456         if ( Kleo::VerifyDetachedJob * const job = cryptProto->verifyDetachedJob() ) {
00457           QByteArray plainData = cleartext;
00458           plainData.resize( cleartext.size() - 1 );
00459           result = job->exec( signaturetext, plainData );
00460           messagePart.auditLog = job->auditLogAsHtml();
00461         } else {
00462           cryptPlugError = CANT_VERIFY_SIGNATURES;
00463         }
00464       } else { // opaque
00465         if ( Kleo::VerifyOpaqueJob * const job = cryptProto->verifyOpaqueJob() ) {
00466           QByteArray plainData;
00467           result = job->exec( signaturetext, plainData );
00468           cleartext = QCString( plainData.data(), plainData.size() + 1 );
00469           messagePart.auditLog = job->auditLogAsHtml();
00470         } else {
00471           cryptPlugError = CANT_VERIFY_SIGNATURES;
00472         }
00473       }
00474       signatures = result.signatures();
00475     }
00476 
00477     if ( doCheck )
00478       kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: returned from CRYPTPLUG" << endl;
00479 
00480     // ### only one signature supported
00481     if ( signatures.size() > 0 ) {
00482       kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: found signature" << endl;
00483       GpgME::Signature signature = signatures[0];
00484 
00485       messagePart.status_code = signatureToStatus( signature );
00486       messagePart.status = QString::fromUtf8( signature.status().asString() );
00487       for ( uint i = 1; i < signatures.size(); ++i ) {
00488         if ( signatureToStatus( signatures[i] ) != messagePart.status_code ) {
00489           messagePart.status_code = GPGME_SIG_STAT_DIFF;
00490           messagePart.status = i18n("Different results for signatures");
00491         }
00492       }
00493       if ( messagePart.status_code & GPGME_SIG_STAT_GOOD )
00494         messagePart.isGoodSignature = true;
00495 
00496       // get key for this signature
00497       Kleo::KeyListJob *job = cryptProto->keyListJob();
00498       std::vector<GpgME::Key> keys;
00499       GpgME::KeyListResult keyListRes = job->exec( QString::fromLatin1( signature.fingerprint() ), false, keys );
00500       GpgME::Key key;
00501       if ( keys.size() == 1 )
00502         key = keys[0];
00503       else if ( keys.size() > 1 )
00504         assert( false ); // ### wtf, what should we do in this case??
00505 
00506       // save extended signature status flags
00507       messagePart.sigSummary = signature.summary();
00508 
00509       if ( key.keyID() )
00510         messagePart.keyId = key.keyID();
00511       if ( messagePart.keyId.isEmpty() )
00512         messagePart.keyId = signature.fingerprint();
00513       // ### Ugh. We depend on two enums being in sync:
00514       messagePart.keyTrust = (Kpgp::Validity)signature.validity();
00515       if ( key.numUserIDs() > 0 && key.userID( 0 ).id() )
00516         messagePart.signer = Kleo::DN( key.userID( 0 ).id() ).prettyDN();
00517       for ( uint iMail = 0; iMail < key.numUserIDs(); ++iMail ) {
00518         // The following if /should/ always result in TRUE but we
00519         // won't trust implicitely the plugin that gave us these data.
00520         if ( key.userID( iMail ).email() ) {
00521           QString email = QString::fromUtf8( key.userID( iMail ).email() );
00522           // ### work around gpgme 0.3.x / cryptplug bug where the
00523           // ### email addresses are specified as angle-addr, not addr-spec:
00524           if ( email.startsWith( "<" ) && email.endsWith( ">" ) )
00525             email = email.mid( 1, email.length() - 2 );
00526           if ( !email.isEmpty() )
00527             messagePart.signerMailAddresses.append( email );
00528         }
00529       }
00530 
00531       if ( signature.creationTime() )
00532         messagePart.creationTime.setTime_t( signature.creationTime() );
00533       else
00534         messagePart.creationTime = QDateTime();
00535       if ( messagePart.signer.isEmpty() ) {
00536         if ( key.numUserIDs() > 0 && key.userID( 0 ).name() )
00537           messagePart.signer = Kleo::DN( key.userID( 0 ).name() ).prettyDN();
00538         if ( !messagePart.signerMailAddresses.empty() ) {
00539           if ( messagePart.signer.isEmpty() )
00540             messagePart.signer = messagePart.signerMailAddresses.front();
00541           else
00542             messagePart.signer += " <" + messagePart.signerMailAddresses.front() + '>';
00543         }
00544       }
00545 
00546       kdDebug(5006) << "\n  key id: " << messagePart.keyId
00547                     << "\n  key trust: " << messagePart.keyTrust
00548                     << "\n  signer: " << messagePart.signer << endl;
00549 
00550     } else {
00551       messagePart.creationTime = QDateTime();
00552     }
00553 
00554     if ( !doCheck || !data ){
00555       if ( cleartextData || !cleartext.isEmpty() ) {
00556         if ( mReader )
00557           htmlWriter()->queue( writeSigstatHeader( messagePart,
00558                                                    cryptProto,
00559                                                    fromAddress ) );
00560         bIsOpaqueSigned = true;
00561 
00562         CryptoProtocolSaver cpws( this, cryptProto );
00563         insertAndParseNewChildNode( sign, doCheck ? cleartext.data() : cleartextData->data(),
00564                                     "opaqued signed data" );
00565 
00566         if ( mReader )
00567           htmlWriter()->queue( writeSigstatFooter( messagePart ) );
00568 
00569       }
00570       else if ( !hideErrors ) {
00571         QString txt;
00572         txt = "<hr><b><h2>";
00573         txt.append( i18n( "The crypto engine returned no cleartext data." ) );
00574         txt.append( "</h2></b>" );
00575         txt.append( "<br>&nbsp;<br>" );
00576         txt.append( i18n( "Status: " ) );
00577         if ( !messagePart.status.isEmpty() ) {
00578           txt.append( "<i>" );
00579           txt.append( messagePart.status );
00580           txt.append( "</i>" );
00581         }
00582         else
00583           txt.append( i18n("(unknown)") );
00584         if ( mReader )
00585           htmlWriter()->queue(txt);
00586       }
00587     }
00588     else {
00589       if ( mReader ) {
00590         if ( !cryptProto ) {
00591           QString errorMsg;
00592           switch ( cryptPlugError ) {
00593           case NOT_INITIALIZED:
00594             errorMsg = i18n( "Crypto plug-in \"%1\" is not initialized." )
00595                        .arg( cryptPlugLibName );
00596             break;
00597           case CANT_VERIFY_SIGNATURES:
00598             errorMsg = i18n( "Crypto plug-in \"%1\" cannot verify signatures." )
00599                        .arg( cryptPlugLibName );
00600             break;
00601           case NO_PLUGIN:
00602             if ( cryptPlugDisplayName.isEmpty() )
00603               errorMsg = i18n( "No appropriate crypto plug-in was found." );
00604             else
00605               errorMsg = i18n( "%1 is either 'OpenPGP' or 'S/MIME'",
00606                                "No %1 plug-in was found." )
00607                            .arg( cryptPlugDisplayName );
00608             break;
00609           }
00610           messagePart.errorText = i18n( "The message is signed, but the "
00611                                         "validity of the signature cannot be "
00612                                         "verified.<br />"
00613                                         "Reason: %1" )
00614                                   .arg( errorMsg );
00615         }
00616 
00617         if ( mReader )
00618           htmlWriter()->queue( writeSigstatHeader( messagePart,
00619                                                    cryptProto,
00620                                                  fromAddress ) );
00621       }
00622 
00623       ObjectTreeParser otp( mReader, cryptProto, true );
00624       otp.parseObjectTree( data );
00625       mRawReplyString += otp.rawReplyString();
00626       mTextualContent += otp.textualContent();
00627       if ( !otp.textualContentCharset().isEmpty() )
00628         mTextualContentCharset = otp.textualContentCharset();
00629 
00630       if ( mReader )
00631         htmlWriter()->queue( writeSigstatFooter( messagePart ) );
00632     }
00633 
00634     kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: done, returning "
00635                   << ( bIsOpaqueSigned ? "TRUE" : "FALSE" ) << endl;
00636     return bIsOpaqueSigned;
00637   }
00638 
00639 
00640 bool ObjectTreeParser::okDecryptMIME( partNode& data,
00641                                       QCString& decryptedData,
00642                                       bool& signatureFound,
00643                                       std::vector<GpgME::Signature> &signatures,
00644                                       bool showWarning,
00645                                       bool& passphraseError,
00646                                       bool& actuallyEncrypted,
00647                                       QString& aErrorText,
00648                                       QString& auditLog )
00649 {
00650   passphraseError = false;
00651   aErrorText = QString::null;
00652   auditLog = QString::null;
00653   bool bDecryptionOk = false;
00654   enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT }
00655     cryptPlugError = NO_PLUGIN;
00656 
00657   const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
00658 
00659   QString cryptPlugLibName;
00660   if ( cryptProto )
00661     cryptPlugLibName = cryptProto->name();
00662 
00663   if ( mReader && !mReader->decryptMessage() ) {
00664     QString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small );
00665     decryptedData = "<div style=\"font-size:large; text-align:center;"
00666                       "padding-top:20pt;\">"
00667                     + i18n("This message is encrypted.").utf8()
00668                     + "</div>"
00669                       "<div style=\"text-align:center; padding-bottom:20pt;\">"
00670                       "<a href=\"kmail:decryptMessage\">"
00671                       "<img src=\"" + iconName.utf8() + "\"/>"
00672                     + i18n("Decrypt Message").utf8()
00673                     + "</a></div>";
00674     return false;
00675   }
00676 
00677   if ( cryptProto && !kmkernel->contextMenuShown() ) {
00678     QByteArray ciphertext( data.msgPart().bodyDecodedBinary() );
00679 #ifdef MARCS_DEBUG
00680     QCString cipherStr( ciphertext.data(), ciphertext.size() + 1 );
00681     bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) &&
00682                           (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) &&
00683                           (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) );
00684 
00685     dumpToFile( "dat_04_reader.encrypted", ciphertext.data(), ciphertext.size() );
00686 
00687     QCString deb;
00688     deb =  "\n\nE N C R Y P T E D    D A T A = ";
00689     if ( cipherIsBinary )
00690       deb += "[binary data]";
00691     else {
00692       deb += "\"";
00693       deb += cipherStr;
00694       deb += "\"";
00695     }
00696     deb += "\n\n";
00697     kdDebug(5006) << deb << endl;
00698 #endif
00699 
00700 
00701     kdDebug(5006) << "ObjectTreeParser::decryptMIME: going to call CRYPTPLUG "
00702                   << cryptPlugLibName << endl;
00703     if ( mReader )
00704       emit mReader->noDrag(); // in case pineentry pops up, don't let kmheaders start a drag afterwards
00705 
00706     Kleo::DecryptVerifyJob* job = cryptProto->decryptVerifyJob();
00707     if ( !job ) {
00708       cryptPlugError = CANT_DECRYPT;
00709       cryptProto = 0;
00710     } else {
00711       QByteArray plainText;
00712       const std::pair<GpgME::DecryptionResult,GpgME::VerificationResult> res = job->exec( ciphertext, plainText );
00713       const GpgME::DecryptionResult & decryptResult = res.first;
00714       const GpgME::VerificationResult & verifyResult = res.second;
00715       signatureFound = verifyResult.signatures().size() > 0;
00716       signatures = verifyResult.signatures();
00717       bDecryptionOk = !decryptResult.error();
00718       passphraseError =  decryptResult.error().isCanceled()
00719         || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
00720       actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA;
00721       aErrorText = QString::fromLocal8Bit( decryptResult.error().asString() );
00722       auditLog = job->auditLogAsHtml();
00723 
00724       kdDebug(5006) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG"
00725                     << endl;
00726       if ( bDecryptionOk )
00727         decryptedData = QCString( plainText.data(), plainText.size() + 1 );
00728       else if ( mReader && showWarning ) {
00729         decryptedData = "<div style=\"font-size:x-large; text-align:center;"
00730                         "padding:20pt;\">"
00731                       + i18n("Encrypted data not shown.").utf8()
00732                       + "</div>";
00733         if ( !passphraseError )
00734           aErrorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.")
00735                         .arg( cryptPlugLibName )
00736                     + "<br />"
00737                     + i18n("Error: %1").arg( aErrorText );
00738       }
00739     }
00740   }
00741 
00742   if ( !cryptProto ) {
00743     decryptedData = "<div style=\"text-align:center; padding:20pt;\">"
00744                   + i18n("Encrypted data not shown.").utf8()
00745                   + "</div>";
00746     switch ( cryptPlugError ) {
00747     case NOT_INITIALIZED:
00748       aErrorText = i18n( "Crypto plug-in \"%1\" is not initialized." )
00749                      .arg( cryptPlugLibName );
00750       break;
00751     case CANT_DECRYPT:
00752       aErrorText = i18n( "Crypto plug-in \"%1\" cannot decrypt messages." )
00753                      .arg( cryptPlugLibName );
00754       break;
00755     case NO_PLUGIN:
00756       aErrorText = i18n( "No appropriate crypto plug-in was found." );
00757       break;
00758     }
00759   } else if ( kmkernel->contextMenuShown() ) {
00760     // ### Workaround for bug 56693 (kmail freeze with the complete desktop
00761     // ### while pinentry-qt appears)
00762     QByteArray ciphertext( data.msgPart().bodyDecodedBinary() );
00763     QCString cipherStr( ciphertext.data(), ciphertext.size() + 1 );
00764     bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) &&
00765                           (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) &&
00766                           (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) );
00767     if ( !cipherIsBinary ) {
00768       decryptedData = cipherStr;
00769     }
00770     else {
00771       decryptedData = "<div style=\"font-size:x-large; text-align:center;"
00772                       "padding:20pt;\">"
00773                     + i18n("Encrypted data not shown.").utf8()
00774                     + "</div>";
00775     }
00776   }
00777 
00778   dumpToFile( "dat_05_reader.decrypted", decryptedData.data(), decryptedData.size() );
00779 
00780   return bDecryptionOk;
00781 }
00782 
00783   //static
00784   bool ObjectTreeParser::containsExternalReferences( const QCString & str )
00785   {
00786     QRegExp httpRegExp("(\\\"|\\\'|url\\s*\\(\\s*)http[s]?:");
00787     int httpPos = str.find( httpRegExp, 0 );
00788 
00789     while ( httpPos >= 0 ) {
00790       // look backwards for "href"
00791       if ( httpPos > 5 ) {
00792         int hrefPos = str.findRev( "href", httpPos - 5, true );
00793         // if no 'href' is found or the distance between 'href' and '"http[s]:'
00794         // is larger than 7 (7 is the distance in 'href = "http[s]:') then
00795         // we assume that we have found an external reference
00796         if ( ( hrefPos == -1 ) || ( httpPos - hrefPos > 7 ) )
00797           return true;
00798       }
00799       // find next occurrence of "http: or "https:
00800       httpPos = str.find( httpRegExp, httpPos + 6 );
00801     }
00802     return false;
00803   }
00804 
00805   bool ObjectTreeParser::processTextHtmlSubtype( partNode * curNode, ProcessResult & ) {
00806     QCString cstr( curNode->msgPart().bodyDecoded() );
00807 
00808     mRawReplyString = cstr;
00809     if ( curNode->isFirstTextPart() ) {
00810       mTextualContent += curNode->msgPart().bodyToUnicode();
00811       mTextualContentCharset = curNode->msgPart().charset();
00812     }
00813 
00814     if ( !mReader )
00815       return true;
00816 
00817     if ( curNode->isFirstTextPart() ||
00818          attachmentStrategy()->defaultDisplay( curNode ) == AttachmentStrategy::Inline ||
00819          showOnlyOneMimePart() )
00820     {
00821       if ( mReader->htmlMail() ) {
00822         // ---Sven's strip </BODY> and </HTML> from end of attachment start-
00823         // We must fo this, or else we will see only 1st inlined html
00824         // attachment.  It is IMHO enough to search only for </BODY> and
00825         // put \0 there.
00826         int i = cstr.findRev("</body>", -1, false); //case insensitive
00827         if ( 0 <= i )
00828           cstr.truncate(i);
00829         else // just in case - search for </html>
00830         {
00831           i = cstr.findRev("</html>", -1, false); //case insensitive
00832           if ( 0 <= i ) cstr.truncate(i);
00833         }
00834         // ---Sven's strip </BODY> and </HTML> from end of attachment end-
00835         // Show the "external references" warning (with possibility to load
00836         // external references only if loading external references is disabled
00837         // and the HTML code contains obvious external references). For
00838         // messages where the external references are obfuscated the user won't
00839         // have an easy way to load them but that shouldn't be a problem
00840         // because only spam contains obfuscated external references.
00841         if ( !mReader->htmlLoadExternal() &&
00842              containsExternalReferences( cstr ) ) {
00843           htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
00844           htmlWriter()->queue( i18n("<b>Note:</b> This HTML message may contain external "
00845                                     "references to images etc. For security/privacy reasons "
00846                                     "external references are not loaded. If you trust the "
00847                                     "sender of this message then you can load the external "
00848                                     "references for this message "
00849                                     "<a href=\"kmail:loadExternal\">by clicking here</a>.") );
00850           htmlWriter()->queue( "</div><br><br>" );
00851         }
00852       } else {
00853         htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
00854         htmlWriter()->queue( i18n("<b>Note:</b> This is an HTML message. For "
00855                                   "security reasons, only the raw HTML code "
00856                                   "is shown. If you trust the sender of this "
00857                                   "message then you can activate formatted "
00858                                   "HTML display for this message "
00859                                   "<a href=\"kmail:showHTML\">by clicking here</a>.") );
00860         htmlWriter()->queue( "</div><br><br>" );
00861       }
00862       htmlWriter()->queue( codecFor( curNode )->toUnicode( mReader->htmlMail() ? cstr : KMMessage::html2source( cstr )));
00863       mReader->mColorBar->setHtmlMode();
00864       return true;
00865     }
00866     return false;
00867   }
00868 } // namespace KMail
00869 
00870 static bool isMailmanMessage( partNode * curNode ) {
00871   if ( !curNode->dwPart() || !curNode->dwPart()->hasHeaders() )
00872     return false;
00873   DwHeaders & headers = curNode->dwPart()->Headers();
00874   if ( headers.HasField("X-Mailman-Version") )
00875     return true;
00876   if ( headers.HasField("X-Mailer") &&
00877        0 == QCString( headers.FieldBody("X-Mailer").AsString().c_str() )
00878        .find("MAILMAN", 0, false) )
00879     return true;
00880   return false;
00881 }
00882 
00883 namespace KMail {
00884 
00885   bool ObjectTreeParser::processMailmanMessage( partNode * curNode ) {
00886     const QCString cstr = curNode->msgPart().bodyDecoded();
00887 
00888     //###
00889     const QCString delim1( "--__--__--\n\nMessage:");
00890     const QCString delim2( "--__--__--\r\n\r\nMessage:");
00891     const QCString delimZ2("--__--__--\n\n_____________");
00892     const QCString delimZ1("--__--__--\r\n\r\n_____________");
00893     QCString partStr, digestHeaderStr;
00894     int thisDelim = cstr.find(delim1, 0, false);
00895     if ( thisDelim == -1 )
00896       thisDelim = cstr.find(delim2, 0, false);
00897     if ( thisDelim == -1 ) {
00898       kdDebug(5006) << "        Sorry: Old style Mailman message but no delimiter found." << endl;
00899       return false;
00900     }
00901 
00902     int nextDelim = cstr.find(delim1, thisDelim+1, false);
00903     if ( -1 == nextDelim )
00904       nextDelim = cstr.find(delim2, thisDelim+1, false);
00905     if ( -1 == nextDelim )
00906       nextDelim = cstr.find(delimZ1, thisDelim+1, false);
00907     if ( -1 == nextDelim )
00908       nextDelim = cstr.find(delimZ2, thisDelim+1, false);
00909     if ( nextDelim < 0)
00910       return false;
00911 
00912     kdDebug(5006) << "        processing old style Mailman digest" << endl;
00913     //if ( curNode->mRoot )
00914     //  curNode = curNode->mRoot;
00915 
00916     // at least one message found: build a mime tree
00917     digestHeaderStr = "Content-Type=text/plain\nContent-Description=digest header\n\n";
00918     digestHeaderStr += cstr.mid( 0, thisDelim );
00919     insertAndParseNewChildNode( *curNode,
00920                                 &*digestHeaderStr,
00921                                 "Digest Header", true );
00922     //mReader->queueHtml("<br><hr><br>");
00923     // temporarily change curent node's Content-Type
00924     // to get our embedded RfC822 messages properly inserted
00925     curNode->setType(    DwMime::kTypeMultipart );
00926     curNode->setSubType( DwMime::kSubtypeDigest );
00927     while( -1 < nextDelim ){
00928       int thisEoL = cstr.find("\nMessage:", thisDelim, false);
00929       if ( -1 < thisEoL )
00930         thisDelim = thisEoL+1;
00931       else{
00932         thisEoL = cstr.find("\n_____________", thisDelim, false);
00933         if ( -1 < thisEoL )
00934           thisDelim = thisEoL+1;
00935       }
00936       thisEoL = cstr.find('\n', thisDelim);
00937       if ( -1 < thisEoL )
00938         thisDelim = thisEoL+1;
00939       else
00940         thisDelim = thisDelim+1;
00941       //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] )
00942       //  ++thisDelim;
00943 
00944       partStr = "Content-Type=message/rfc822\nContent-Description=embedded message\n";
00945       partStr += cstr.mid( thisDelim, nextDelim-thisDelim );
00946       QCString subject("embedded message");
00947       QCString subSearch("\nSubject:");
00948       int subPos = partStr.find(subSearch, 0, false);
00949       if ( -1 < subPos ){
00950         subject = partStr.mid(subPos+subSearch.length());
00951         thisEoL = subject.find('\n');
00952         if ( -1 < thisEoL )
00953           subject.truncate( thisEoL );
00954       }
00955       kdDebug(5006) << "        embedded message found: \"" << subject << "\"" << endl;
00956       insertAndParseNewChildNode( *curNode,
00957                                   &*partStr,
00958                                   subject, true );
00959       //mReader->queueHtml("<br><hr><br>");
00960       thisDelim = nextDelim+1;
00961       nextDelim = cstr.find(delim1, thisDelim, false);
00962       if ( -1 == nextDelim )
00963         nextDelim = cstr.find(delim2, thisDelim, false);
00964       if ( -1 == nextDelim )
00965         nextDelim = cstr.find(delimZ1, thisDelim, false);
00966       if ( -1 == nextDelim )
00967         nextDelim = cstr.find(delimZ2, thisDelim, false);
00968     }
00969     // reset curent node's Content-Type
00970     curNode->setType(    DwMime::kTypeText );
00971     curNode->setSubType( DwMime::kSubtypePlain );
00972     int thisEoL = cstr.find("_____________", thisDelim);
00973     if ( -1 < thisEoL ){
00974       thisDelim = thisEoL;
00975       thisEoL = cstr.find('\n', thisDelim);
00976       if ( -1 < thisEoL )
00977         thisDelim = thisEoL+1;
00978     }
00979     else
00980       thisDelim = thisDelim+1;
00981     partStr = "Content-Type=text/plain\nContent-Description=digest footer\n\n";
00982     partStr += cstr.mid( thisDelim );
00983     insertAndParseNewChildNode( *curNode,
00984                                 &*partStr,
00985                                 "Digest Footer", true );
00986     return true;
00987   }
00988 
00989   bool ObjectTreeParser::processTextPlainSubtype( partNode * curNode, ProcessResult & result ) {
00990     if ( !mReader ) {
00991       mRawReplyString = curNode->msgPart().bodyDecoded();
00992       if ( curNode->isFirstTextPart() ) {
00993         mTextualContent += curNode->msgPart().bodyToUnicode();
00994         mTextualContentCharset = curNode->msgPart().charset();
00995       }
00996       return true;
00997     }
00998 
00999     if ( !curNode->isFirstTextPart() &&
01000          attachmentStrategy()->defaultDisplay( curNode ) != AttachmentStrategy::Inline &&
01001          !showOnlyOneMimePart() )
01002       return false;
01003 
01004     mRawReplyString = curNode->msgPart().bodyDecoded();
01005     if ( curNode->isFirstTextPart() ) {
01006       mTextualContent += curNode->msgPart().bodyToUnicode();
01007       mTextualContentCharset = curNode->msgPart().charset();
01008     }
01009 
01010     QString label = curNode->msgPart().fileName().stripWhiteSpace();
01011     if ( label.isEmpty() )
01012       label = curNode->msgPart().name().stripWhiteSpace();
01013 
01014     const bool bDrawFrame = !curNode->isFirstTextPart()
01015                           && !showOnlyOneMimePart()
01016                           && !label.isEmpty();
01017     if ( bDrawFrame ) {
01018       label = KMMessage::quoteHtmlChars( label, true );
01019 
01020       const QString comment =
01021         KMMessage::quoteHtmlChars( curNode->msgPart().contentDescription(), true );
01022 
01023       const QString fileName =
01024         mReader->writeMessagePartToTempFile( &curNode->msgPart(),
01025                                              curNode->nodeId() );
01026 
01027       const QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ;
01028 
01029       QString htmlStr = "<table cellspacing=\"1\" class=\"textAtm\">"
01030                  "<tr class=\"textAtmH\"><td dir=\"" + dir + "\">";
01031       if ( !fileName.isEmpty() )
01032         htmlStr += "<a href=\"" + QString("file:")
01033           + KURL::encode_string( fileName ) + "\">"
01034           + label + "</a>";
01035       else
01036         htmlStr += label;
01037       if ( !comment.isEmpty() )
01038         htmlStr += "<br>" + comment;
01039       htmlStr += "</td></tr><tr class=\"textAtmB\"><td>";
01040 
01041       htmlWriter()->queue( htmlStr );
01042     }
01043     // process old style not-multipart Mailman messages to
01044     // enable verification of the embedded messages' signatures
01045     if ( !isMailmanMessage( curNode ) ||
01046          !processMailmanMessage( curNode ) )
01047       writeBodyString( mRawReplyString, curNode->trueFromAddress(),
01048                        codecFor( curNode ), result, !bDrawFrame );
01049     if ( bDrawFrame )
01050       htmlWriter()->queue( "</td></tr></table>" );
01051 
01052     return true;
01053   }
01054 
01055   void ObjectTreeParser::stdChildHandling( partNode * child ) {
01056     if ( !child )
01057       return;
01058 
01059     ObjectTreeParser otp( *this );
01060     otp.setShowOnlyOneMimePart( false );
01061     otp.parseObjectTree( child );
01062     mRawReplyString += otp.rawReplyString();
01063     mTextualContent += otp.textualContent();
01064     if ( !otp.textualContentCharset().isEmpty() )
01065       mTextualContentCharset = otp.textualContentCharset();
01066   }
01067 
01068   bool ObjectTreeParser::processMultiPartMixedSubtype( partNode * node, ProcessResult & ) {
01069     partNode * child = node->firstChild();
01070     if ( !child )
01071       return false;
01072 
01073     // normal treatment of the parts in the mp/mixed container
01074     stdChildHandling( child );
01075     return true;
01076   }
01077 
01078   bool ObjectTreeParser::processMultiPartAlternativeSubtype( partNode * node, ProcessResult & ) {
01079     partNode * child = node->firstChild();
01080     if ( !child )
01081       return false;
01082 
01083     partNode * dataHtml = child->findType( DwMime::kTypeText,
01084                                            DwMime::kSubtypeHtml, false, true );
01085     partNode * dataPlain = child->findType( DwMime::kTypeText,
01086                                             DwMime::kSubtypePlain, false, true );
01087 
01088     if ( (mReader && mReader->htmlMail() && dataHtml) ||
01089          (dataHtml && dataPlain && dataPlain->msgPart().body().isEmpty()) ) {
01090       if ( dataPlain )
01091         dataPlain->setProcessed( true, false );
01092       stdChildHandling( dataHtml );
01093       return true;
01094     }
01095 
01096     if ( !mReader || (!mReader->htmlMail() && dataPlain) ) {
01097       if ( dataHtml )
01098         dataHtml->setProcessed( true, false );
01099       stdChildHandling( dataPlain );
01100       return true;
01101     }
01102 
01103     stdChildHandling( child );
01104     return true;
01105   }
01106 
01107   bool ObjectTreeParser::processMultiPartDigestSubtype( partNode * node, ProcessResult & result ) {
01108     return processMultiPartMixedSubtype( node, result );
01109   }
01110 
01111   bool ObjectTreeParser::processMultiPartParallelSubtype( partNode * node, ProcessResult & result ) {
01112     return processMultiPartMixedSubtype( node, result );
01113   }
01114 
01115   bool ObjectTreeParser::processMultiPartSignedSubtype( partNode * node, ProcessResult & ) {
01116     if ( node->childCount() != 2 ) {
01117       kdDebug(5006) << "mulitpart/signed must have exactly two child parts!" << endl
01118                     << "processing as multipart/mixed" << endl;
01119       if ( node->firstChild() )
01120         stdChildHandling( node->firstChild() );
01121       return node->firstChild();
01122     }
01123 
01124     partNode * signedData = node->firstChild();
01125     assert( signedData );
01126 
01127     partNode * signature = signedData->nextSibling();
01128     assert( signature );
01129 
01130     signature->setProcessed( true, true );
01131 
01132     if ( !includeSignatures() ) {
01133       stdChildHandling( signedData );
01134       return true;
01135     }
01136 
01137     // FIXME(marc) check here that the protocol parameter matches the
01138     // mimetype of "signature" (not required by the RFC, but practised
01139     // by all implementaions of security multiparts
01140 
01141     const QString contentType = node->contentTypeParameter( "protocol" ).lower();
01142     const Kleo::CryptoBackend::Protocol *protocol = 0;
01143     if ( contentType == "application/pkcs7-signature" || contentType == "application/x-pkcs7-signature" )
01144       protocol = Kleo::CryptoBackendFactory::instance()->smime();
01145     else if ( contentType == "application/pgp-signature" || contentType == "application/x-pgp-signature" )
01146       protocol = Kleo::CryptoBackendFactory::instance()->openpgp();
01147 
01148     if ( !protocol ) {
01149       signature->setProcessed( true, true );
01150       stdChildHandling( signedData );
01151       return true;
01152     }
01153 
01154     CryptoProtocolSaver saver( this, protocol );
01155 
01156     node->setSignatureState( KMMsgFullySigned );
01157     writeOpaqueOrMultipartSignedData( signedData, *signature,
01158                                       node->trueFromAddress() );
01159     return true;
01160   }
01161 
01162   bool ObjectTreeParser::processMultiPartEncryptedSubtype( partNode * node, ProcessResult & result ) {
01163     partNode * child = node->firstChild();
01164     if ( !child )
01165       return false;
01166 
01167     if ( keepEncryptions() ) {
01168       node->setEncryptionState( KMMsgFullyEncrypted );
01169       const QCString cstr = node->msgPart().bodyDecoded();
01170       if ( mReader )
01171         writeBodyString( cstr, node->trueFromAddress(),
01172                          codecFor( node ), result, false );
01173       mRawReplyString += cstr;
01174       return true;
01175     }
01176 
01177     const Kleo::CryptoBackend::Protocol * useThisCryptProto = 0;
01178 
01179     /*
01180       ATTENTION: This code is to be replaced by the new 'auto-detect' feature. --------------------------------------
01181     */
01182     partNode * data = child->findType( DwMime::kTypeApplication,
01183                                        DwMime::kSubtypeOctetStream, false, true );
01184     if ( data ) {
01185       useThisCryptProto = Kleo::CryptoBackendFactory::instance()->openpgp();
01186     }
01187     if ( !data ) {
01188       data = child->findType( DwMime::kTypeApplication,
01189                               DwMime::kSubtypePkcs7Mime, false, true );
01190       if ( data ) {
01191         useThisCryptProto = Kleo::CryptoBackendFactory::instance()->smime();
01192       }
01193     }
01194     /*
01195       ---------------------------------------------------------------------------------------------------------------
01196     */
01197 
01198     if ( !data ) {
01199       stdChildHandling( child );
01200       return true;
01201     }
01202 
01203     CryptoProtocolSaver cpws( this, useThisCryptProto );
01204 
01205     if ( partNode * dataChild = data->firstChild() ) {
01206       kdDebug(5006) << "\n----->  Calling parseObjectTree( curNode->mChild )\n" << endl;
01207       stdChildHandling( dataChild );
01208       kdDebug(5006) << "\n----->  Returning from parseObjectTree( curNode->mChild )\n" << endl;
01209       return true;
01210     }
01211 
01212     kdDebug(5006) << "\n----->  Initially processing encrypted data\n" << endl;
01213     PartMetaData messagePart;
01214     node->setEncryptionState( KMMsgFullyEncrypted );
01215     QCString decryptedData;
01216     bool signatureFound;
01217     std::vector<GpgME::Signature> signatures;
01218     bool passphraseError;
01219     bool actuallyEncrypted = true;
01220 
01221     bool bOkDecrypt = okDecryptMIME( *data,
01222                                      decryptedData,
01223                                      signatureFound,
01224                                      signatures,
01225                                      true,
01226                                      passphraseError,
01227                                      actuallyEncrypted,
01228                                      messagePart.errorText,
01229                                      messagePart.auditLog );
01230 
01231     // paint the frame
01232     if ( mReader ) {
01233       messagePart.isDecryptable = bOkDecrypt;
01234       messagePart.isEncrypted = true;
01235       messagePart.isSigned = false;
01236       htmlWriter()->queue( writeSigstatHeader( messagePart,
01237                                                cryptoProtocol(),
01238                                                node->trueFromAddress() ) );
01239     }
01240 
01241     if ( bOkDecrypt ) {
01242       // Note: Multipart/Encrypted might also be signed
01243       //       without encapsulating a nicely formatted
01244       //       ~~~~~~~                 Multipart/Signed part.
01245       //                               (see RFC 3156 --> 6.2)
01246       // In this case we paint a _2nd_ frame inside the
01247       // encryption frame, but we do _not_ show a respective
01248       // encapsulated MIME part in the Mime Tree Viewer
01249       // since we do want to show the _true_ structure of the
01250       // message there - not the structure that the sender's
01251       // MUA 'should' have sent.  :-D       (khz, 12.09.2002)
01252       //
01253       if ( signatureFound ) {
01254         writeOpaqueOrMultipartSignedData( 0,
01255                                           *node,
01256                                           node->trueFromAddress(),
01257                                           false,
01258                                           &decryptedData,
01259                                           signatures,
01260                                           false );
01261         node->setSignatureState( KMMsgFullySigned );
01262       } else {
01263         insertAndParseNewChildNode( *node,
01264                                     &*decryptedData,
01265                                     "encrypted data" );
01266       }
01267     } else {
01268       mRawReplyString += decryptedData;
01269       if ( mReader ) {
01270         // print the error message that was returned in decryptedData
01271         // (utf8-encoded)
01272         htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) );
01273       }
01274     }
01275 
01276     if ( mReader )
01277       htmlWriter()->queue( writeSigstatFooter( messagePart ) );
01278     data->setProcessed( true, false ); // Set the data node to done to prevent it from being processed
01279     return true;
01280   }
01281 
01282 
01283   bool ObjectTreeParser::processMessageRfc822Subtype( partNode * node, ProcessResult & ) {
01284     if ( mReader
01285          && !attachmentStrategy()->inlineNestedMessages()
01286          && !showOnlyOneMimePart() )
01287       return false;
01288 
01289     if ( partNode * child = node->firstChild() ) {
01290       kdDebug(5006) << "\n----->  Calling parseObjectTree( curNode->mChild )\n" << endl;
01291       ObjectTreeParser otp( mReader, cryptoProtocol() );
01292       otp.parseObjectTree( child );
01293       mRawReplyString += otp.rawReplyString();
01294       mTextualContent += otp.textualContent();
01295       if ( !otp.textualContentCharset().isEmpty() )
01296         mTextualContentCharset = otp.textualContentCharset();
01297       kdDebug(5006) << "\n<-----  Returning from parseObjectTree( curNode->mChild )\n" << endl;
01298       return true;
01299     }
01300     kdDebug(5006) << "\n----->  Initially processing data of embedded RfC 822 message\n" << endl;
01301     // paint the frame
01302     PartMetaData messagePart;
01303     if ( mReader ) {
01304       messagePart.isEncrypted = false;
01305       messagePart.isSigned = false;
01306       messagePart.isEncapsulatedRfc822Message = true;
01307       QString filename =
01308         mReader->writeMessagePartToTempFile( &node->msgPart(),
01309                                             node->nodeId() );
01310       htmlWriter()->queue( writeSigstatHeader( messagePart,
01311                                                cryptoProtocol(),
01312                                                node->trueFromAddress(),
01313                                                filename ) );
01314     }
01315     QCString rfc822messageStr( node->msgPart().bodyDecoded() );
01316     // display the headers of the encapsulated message
01317     DwMessage* rfc822DwMessage = new DwMessage(); // will be deleted by c'tor of rfc822headers
01318     rfc822DwMessage->FromString( rfc822messageStr );
01319     rfc822DwMessage->Parse();
01320     KMMessage rfc822message( rfc822DwMessage );
01321     node->setFromAddress( rfc822message.from() );
01322     kdDebug(5006) << "\n----->  Store RfC 822 message header \"From: " << rfc822message.from() << "\"\n" << endl;
01323     if ( mReader )
01324       htmlWriter()->queue( mReader->writeMsgHeader( &rfc822message ) );
01325       //mReader->parseMsgHeader( &rfc822message );
01326     // display the body of the encapsulated message
01327     insertAndParseNewChildNode( *node,
01328                                 &*rfc822messageStr,
01329                                 "encapsulated message" );
01330     if ( mReader )
01331       htmlWriter()->queue( writeSigstatFooter( messagePart ) );
01332     return true;
01333   }
01334 
01335 
01336   bool ObjectTreeParser::processApplicationOctetStreamSubtype( partNode * node, ProcessResult & result ) {
01337     if ( partNode * child = node->firstChild() ) {
01338       kdDebug(5006) << "\n----->  Calling parseObjectTree( curNode->mChild )\n" << endl;
01339       ObjectTreeParser otp( mReader, cryptoProtocol() );
01340       otp.parseObjectTree( child );
01341       mRawReplyString += otp.rawReplyString();
01342       mTextualContent += otp.textualContent();
01343       if ( !otp.textualContentCharset().isEmpty() )
01344         mTextualContentCharset = otp.textualContentCharset();
01345       kdDebug(5006) << "\n<-----  Returning from parseObjectTree( curNode->mChild )\n" << endl;
01346       return true;
01347     }
01348 
01349     const Kleo::CryptoBackend::Protocol* oldUseThisCryptPlug = cryptoProtocol();
01350     if (    node->parentNode()
01351             && DwMime::kTypeMultipart    == node->parentNode()->type()
01352             && DwMime::kSubtypeEncrypted == node->parentNode()->subType() ) {
01353       kdDebug(5006) << "\n----->  Initially processing encrypted data\n" << endl;
01354       node->setEncryptionState( KMMsgFullyEncrypted );
01355       if ( keepEncryptions() ) {
01356         const QCString cstr = node->msgPart().bodyDecoded();
01357         if ( mReader )
01358           writeBodyString( cstr, node->trueFromAddress(),
01359                            codecFor( node ), result, false );
01360         mRawReplyString += cstr;
01361       } else {
01362         /*
01363           ATTENTION: This code is to be replaced by the planned 'auto-detect' feature.
01364         */
01365         PartMetaData messagePart;
01366         setCryptoProtocol( Kleo::CryptoBackendFactory::instance()->openpgp() );
01367         QCString decryptedData;
01368         bool signatureFound;
01369         std::vector<GpgME::Signature> signatures;
01370         bool passphraseError;
01371         bool actuallyEncrypted = true;
01372 
01373         bool bOkDecrypt = okDecryptMIME( *node,
01374                                          decryptedData,
01375                                          signatureFound,
01376                                          signatures,
01377                                          true,
01378                                          passphraseError,
01379                                          actuallyEncrypted,
01380                                          messagePart.errorText,
01381                                          messagePart.auditLog );
01382 
01383         // paint the frame
01384         if ( mReader ) {
01385           messagePart.isDecryptable = bOkDecrypt;
01386           messagePart.isEncrypted = true;
01387           messagePart.isSigned = false;
01388           htmlWriter()->queue( writeSigstatHeader( messagePart,
01389                                                    cryptoProtocol(),
01390                                                    node->trueFromAddress() ) );
01391         }
01392 
01393         if ( bOkDecrypt ) {
01394           // fixing the missing attachments bug #1090-b
01395           insertAndParseNewChildNode( *node,
01396                                       &*decryptedData,
01397                                       "encrypted data" );
01398         } else {
01399           mRawReplyString += decryptedData;
01400           if ( mReader ) {
01401             // print the error message that was returned in decryptedData
01402             // (utf8-encoded)
01403             htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) );
01404           }
01405         }
01406 
01407         if ( mReader )
01408           htmlWriter()->queue( writeSigstatFooter( messagePart ) );
01409       }
01410       return true;
01411     }
01412     setCryptoProtocol( oldUseThisCryptPlug );
01413     return false;
01414   }
01415 
01416   bool ObjectTreeParser::processApplicationPkcs7MimeSubtype( partNode * node, ProcessResult & result ) {
01417     if ( partNode * child = node->firstChild() ) {
01418       kdDebug(5006) << "\n----->  Calling parseObjectTree( curNode->mChild )\n" << endl;
01419       ObjectTreeParser otp( mReader, cryptoProtocol() );
01420       otp.parseObjectTree( child );
01421       mRawReplyString += otp.rawReplyString();
01422       mTextualContent += otp.textualContent();
01423       if ( !otp.textualContentCharset().isEmpty() )
01424         mTextualContentCharset = otp.textualContentCharset();
01425       kdDebug(5006) << "\n<-----  Returning from parseObjectTree( curNode->mChild )\n" << endl;
01426       return true;
01427     }
01428 
01429     kdDebug(5006) << "\n----->  Initially processing signed and/or encrypted data\n" << endl;
01430     if ( !node->dwPart() || !node->dwPart()->hasHeaders() )
01431       return false;
01432 
01433     const Kleo::CryptoBackend::Protocol * smimeCrypto = Kleo::CryptoBackendFactory::instance()->smime();
01434 
01435     const QString smimeType = node->contentTypeParameter("smime-type").lower();
01436 
01437     if ( smimeType == "certs-only" ) {
01438       result.setNeverDisplayInline( true );
01439       if ( !smimeCrypto || !mReader )
01440         return false;
01441 
01442       const KConfigGroup reader( KMKernel::config(), "Reader" );
01443       if ( !reader.readBoolEntry( "AutoImportKeys", false ) )
01444         return false;
01445 
01446       const QByteArray certData = node->msgPart().bodyDecodedBinary();
01447 
01448       Kleo::ImportJob *import = smimeCrypto->importJob();
01449       const GpgME::ImportResult res = import->exec( certData );
01450       if ( res.error() ) {
01451         htmlWriter()->queue( i18n( "Sorry, certificate could not be imported.<br>"
01452                                    "Reason: %1").arg( QString::fromLocal8Bit( res.error().asString() ) ) );
01453         return true;
01454       }
01455 
01456       const int nImp = res.numImported();
01457       const int nUnc = res.numUnchanged();
01458       const int nSKImp = res.numSecretKeysImported();
01459       const int nSKUnc = res.numSecretKeysUnchanged();
01460       if ( !nImp && !nSKImp && !nUnc && !nSKUnc ) {
01461         htmlWriter()->queue( i18n( "Sorry, no certificates were found in this message." ) );
01462         return true;
01463       }
01464       QString comment = "<b>" + i18n( "Certificate import status:" ) + "</b><br>&nbsp;<br>";
01465       if ( nImp )
01466         comment += i18n( "1 new certificate was imported.",
01467                          "%n new certificates were imported.", nImp ) + "<br>";
01468       if ( nUnc )
01469         comment += i18n( "1 certificate was unchanged.",
01470                          "%n certificates were unchanged.", nUnc ) + "<br>";
01471       if ( nSKImp )
01472         comment += i18n( "1 new secret key was imported.",
01473                          "%n new secret keys were imported.", nSKImp ) + "<br>";
01474       if ( nSKUnc )
01475         comment += i18n( "1 secret key was unchanged.",
01476                          "%n secret keys were unchanged.", nSKUnc ) + "<br>";
01477       comment += "&nbsp;<br>";
01478       htmlWriter()->queue( comment );
01479       if ( !nImp && !nSKImp ) {
01480         htmlWriter()->queue( "<hr>" );
01481         return true;
01482       }
01483       const std::vector<GpgME::Import> imports = res.imports();
01484       if ( imports.empty() ) {
01485         htmlWriter()->queue( i18n( "Sorry, no details on certificate import available." ) + "<hr>" );
01486         return true;
01487       }
01488       htmlWriter()->queue( "<b>" + i18n( "Certificate import details:" ) + "</b><br>" );
01489       for ( std::vector<GpgME::Import>::const_iterator it = imports.begin() ; it != imports.end() ; ++it ) {
01490         if ( (*it).error() )
01491           htmlWriter()->queue( i18n( "Failed: %1 (%2)" )
01492                                .arg( (*it).fingerprint(),
01493                                      QString::fromLocal8Bit( (*it).error().asString() ) ) );
01494         else if ( (*it).status() & ~GpgME::Import::ContainedSecretKey )
01495           if ( (*it).status() & GpgME::Import::ContainedSecretKey )
01496             htmlWriter()->queue( i18n( "New or changed: %1 (secret key available)" ).arg( (*it).fingerprint() ) );
01497           else
01498             htmlWriter()->queue( i18n( "New or changed: %1" ).arg( (*it).fingerprint() ) );
01499         htmlWriter()->queue( "<br>" );
01500       }
01501 
01502       htmlWriter()->queue( "<hr>" );
01503       return true;
01504     }
01505 
01506     if ( !smimeCrypto )
01507       return false;
01508     CryptoProtocolSaver cpws( this, smimeCrypto );
01509 
01510     bool isSigned      = smimeType == "signed-data";
01511     bool isEncrypted   = smimeType == "enveloped-data";
01512 
01513     // Analyze "signTestNode" node to find/verify a signature.
01514     // If zero this verification was successfully done after
01515     // decrypting via recursion by insertAndParseNewChildNode().
01516     partNode* signTestNode = isEncrypted ? 0 : node;
01517 
01518 
01519     // We try decrypting the content
01520     // if we either *know* that it is an encrypted message part
01521     // or there is neither signed nor encrypted parameter.
01522     if ( !isSigned ) {
01523       if ( isEncrypted )
01524         kdDebug(5006) << "pkcs7 mime     ==      S/MIME TYPE: enveloped (encrypted) data" << endl;
01525       else
01526         kdDebug(5006) << "pkcs7 mime  -  type unknown  -  enveloped (encrypted) data ?" << endl;
01527       QCString decryptedData;
01528       PartMetaData messagePart;
01529       messagePart.isEncrypted = true;
01530       messagePart.isSigned = false;
01531       bool signatureFound;
01532       std::vector<GpgME::Signature> signatures;
01533       bool passphraseError;
01534       bool actuallyEncrypted = true;
01535 
01536       if ( okDecryptMIME( *node,
01537                           decryptedData,
01538                           signatureFound,
01539                           signatures,
01540                           false,
01541                           passphraseError,
01542                           actuallyEncrypted,
01543                           messagePart.errorText,
01544                           messagePart.auditLog ) ) {
01545         kdDebug(5006) << "pkcs7 mime  -  encryption found  -  enveloped (encrypted) data !" << endl;
01546         isEncrypted = true;
01547         node->setEncryptionState( KMMsgFullyEncrypted );
01548         signTestNode = 0;
01549         // paint the frame
01550         messagePart.isDecryptable = true;
01551         if ( mReader )
01552           htmlWriter()->queue( writeSigstatHeader( messagePart,
01553                                                    cryptoProtocol(),
01554                                                    node->trueFromAddress() ) );
01555         insertAndParseNewChildNode( *node,
01556                                     &*decryptedData,
01557                                     "encrypted data" );
01558         if ( mReader )
01559           htmlWriter()->queue( writeSigstatFooter( messagePart ) );
01560       } else {
01561           // decryption failed, which could be because the part was encrypted but
01562           // decryption failed, or because we didn't know if it was encrypted, tried,
01563           // and failed. If the message was not actually encrypted, we continue
01564           // assuming it's signed
01565         if ( passphraseError || ( smimeType.isEmpty() && actuallyEncrypted ) ) {
01566           isEncrypted = true;
01567           signTestNode = 0;
01568         }
01569 
01570         if ( isEncrypted ) {
01571           kdDebug(5006) << "pkcs7 mime  -  ERROR: COULD NOT DECRYPT enveloped data !" << endl;
01572           // paint the frame
01573           messagePart.isDecryptable = false;
01574           if ( mReader ) {
01575             htmlWriter()->queue( writeSigstatHeader( messagePart,
01576                                                      cryptoProtocol(),
01577                                                      node->trueFromAddress() ) );
01578             if ( mReader->decryptMessage() )
01579               writePartIcon( &node->msgPart(), node->nodeId() );
01580             else
01581               htmlWriter()->queue( QString::fromUtf8( decryptedData ) );
01582             htmlWriter()->queue( writeSigstatFooter( messagePart ) );
01583           }
01584         } else {
01585           kdDebug(5006) << "pkcs7 mime  -  NO encryption found" << endl;
01586         }
01587       }
01588       if ( isEncrypted )
01589         node->setEncryptionState( KMMsgFullyEncrypted );
01590     }
01591 
01592     // We now try signature verification if necessarry.
01593     if ( signTestNode ) {
01594       if ( isSigned )
01595         kdDebug(5006) << "pkcs7 mime     ==      S/MIME TYPE: opaque signed data" << endl;
01596       else
01597         kdDebug(5006) << "pkcs7 mime  -  type unknown  -  opaque signed data ?" << endl;
01598 
01599       bool sigFound = writeOpaqueOrMultipartSignedData( 0,
01600                                                         *signTestNode,
01601                                                         node->trueFromAddress(),
01602                                                         true,
01603                                                         0,
01604                                                         std::vector<GpgME::Signature>(),
01605                                                         isEncrypted );
01606       if ( sigFound ) {
01607         if ( !isSigned ) {
01608           kdDebug(5006) << "pkcs7 mime  -  signature found  -  opaque signed data !" << endl;
01609           isSigned = true;
01610         }
01611         signTestNode->setSignatureState( KMMsgFullySigned );
01612         if ( signTestNode != node )
01613           node->setSignatureState( KMMsgFullySigned );
01614       } else {
01615         kdDebug(5006) << "pkcs7 mime  -  NO signature found   :-(" << endl;
01616       }
01617     }
01618 
01619     return isSigned || isEncrypted;
01620 }
01621 
01622 bool ObjectTreeParser::decryptChiasmus( const QByteArray& data, QByteArray& bodyDecoded, QString& errorText )
01623 {
01624   const Kleo::CryptoBackend::Protocol * chiasmus =
01625     Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
01626   Q_ASSERT( chiasmus );
01627   if ( !chiasmus )
01628     return false;
01629 
01630   const STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
01631   if ( !listjob.get() ) {
01632     errorText = i18n( "Chiasmus backend does not offer the "
01633                       "\"x-obtain-keys\" function. Please report this bug." );
01634     return false;
01635   }
01636 
01637   if ( listjob->exec() ) {
01638     errorText = i18n( "Chiasmus Backend Error" );
01639     return false;
01640   }
01641 
01642   const QVariant result = listjob->property( "result" );
01643   if ( result.type() != QVariant::StringList ) {
01644     errorText = i18n( "Unexpected return value from Chiasmus backend: "
01645                       "The \"x-obtain-keys\" function did not return a "
01646                       "string list. Please report this bug." );
01647     return false;
01648   }
01649 
01650   const QStringList keys = result.toStringList();
01651   if ( keys.empty() ) {
01652     errorText = i18n( "No keys have been found. Please check that a "
01653                       "valid key path has been set in the Chiasmus "
01654                       "configuration." );
01655     return false;
01656   }
01657 
01658   emit mReader->noDrag();
01659   ChiasmusKeySelector selectorDlg( mReader, i18n( "Chiasmus Decryption Key Selection" ),
01660                                    keys, GlobalSettings::chiasmusDecryptionKey(),
01661                                    GlobalSettings::chiasmusDecryptionOptions() );
01662   if ( selectorDlg.exec() != QDialog::Accepted )
01663     return false;
01664 
01665   GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() );
01666   GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() );
01667   assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );
01668 
01669   Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", QMap<QString,QVariant>() );
01670   if ( !job ) {
01671     errorText = i18n( "Chiasmus backend does not offer the "
01672                       "\"x-decrypt\" function. Please report this bug." );
01673     return false;
01674   }
01675 
01676   if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
01677        !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
01678        !job->setProperty( "input", data ) ) {
01679     errorText = i18n( "The \"x-decrypt\" function does not accept "
01680                       "the expected parameters. Please report this bug." );
01681     return false;
01682   }
01683 
01684   if ( job->exec() ) {
01685     errorText = i18n( "Chiasmus Decryption Error" );
01686     return false;
01687   }
01688 
01689   const QVariant resultData = job->property( "result" );
01690   if ( resultData.type() != QVariant::ByteArray ) {
01691     errorText = i18n( "Unexpected return value from Chiasmus backend: "
01692                       "The \"x-decrypt\" function did not return a "
01693                       "byte array. Please report this bug." );
01694     return false;
01695   }
01696   bodyDecoded = resultData.toByteArray();
01697   return true;
01698 }
01699 
01700 bool ObjectTreeParser::processApplicationChiasmusTextSubtype( partNode * curNode, ProcessResult & result )
01701 {
01702   if ( !mReader ) {
01703     mRawReplyString = curNode->msgPart().bodyDecoded();
01704     mTextualContent += curNode->msgPart().bodyToUnicode();
01705     mTextualContentCharset = curNode->msgPart().charset();
01706     return true;
01707   }
01708 
01709   QByteArray decryptedBody;
01710   QString errorText;
01711   const QByteArray data = curNode->msgPart().bodyDecodedBinary();
01712   bool bOkDecrypt = decryptChiasmus( data, decryptedBody, errorText );
01713   PartMetaData messagePart;
01714   messagePart.isDecryptable = bOkDecrypt;
01715   messagePart.isEncrypted = true;
01716   messagePart.isSigned = false;
01717   messagePart.errorText = errorText;
01718   if ( mReader )
01719     htmlWriter()->queue( writeSigstatHeader( messagePart,
01720                                              0, //cryptPlugWrapper(),
01721                                              curNode->trueFromAddress() ) );
01722   const QByteArray body = bOkDecrypt ? decryptedBody : data;
01723   const QString chiasmusCharset = curNode->contentTypeParameter("chiasmus-charset");
01724   const QTextCodec* aCodec = chiasmusCharset.isEmpty()
01725     ? codecFor( curNode )
01726     : KMMsgBase::codecForName( chiasmusCharset.ascii() );
01727   htmlWriter()->queue( quotedHTML( aCodec->toUnicode( body ), false /*decorate*/ ) );
01728   result.setInlineEncryptionState( KMMsgFullyEncrypted );
01729   if ( mReader )
01730     htmlWriter()->queue( writeSigstatFooter( messagePart ) );
01731   return true;
01732 }
01733 
01734 bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessResult &result )
01735 {
01736   Q_UNUSED( result );
01737   if ( !mReader )
01738     return false;
01739 
01740   const QString fileName = mReader->writeMessagePartToTempFile( &node->msgPart(), node->nodeId() );
01741   KTNEFParser parser;
01742   if ( !parser.openFile( fileName ) || !parser.message()) {
01743     kdDebug() << k_funcinfo << "Could not parse " << fileName << endl;
01744     return false;
01745   }
01746 
01747   QPtrList<KTNEFAttach> tnefatts = parser.message()->attachmentList();
01748   if ( tnefatts.isEmpty() ) {
01749     kdDebug() << k_funcinfo << "No attachments found in " << fileName << endl;
01750     return false;
01751   }
01752 
01753   if ( !showOnlyOneMimePart() ) {
01754     QString label = node->msgPart().fileName().stripWhiteSpace();
01755     if ( label.isEmpty() )
01756       label = node->msgPart().name().stripWhiteSpace();
01757     label = KMMessage::quoteHtmlChars( label, true );
01758     const QString comment = KMMessage::quoteHtmlChars( node->msgPart().contentDescription(), true );
01759     const QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ;
01760 
01761     QString htmlStr = "<table cellspacing=\"1\" class=\"textAtm\">"
01762                 "<tr class=\"textAtmH\"><td dir=\"" + dir + "\">";
01763     if ( !fileName.isEmpty() )
01764       htmlStr += "<a href=\"" + QString("file:")
01765         + KURL::encode_string( fileName ) + "\">"
01766         + label + "</a>";
01767     else
01768       htmlStr += label;
01769     if ( !comment.isEmpty() )
01770       htmlStr += "<br>" + comment;
01771     htmlStr += "</td></tr><tr class=\"textAtmB\"><td>";
01772     htmlWriter()->queue( htmlStr );
01773   }
01774 
01775   for ( uint i = 0; i < tnefatts.count(); ++i ) {
01776     KTNEFAttach *att = tnefatts.at( i );
01777     QString label = att->displayName();
01778     if( label.isEmpty() )
01779       label = att->name();
01780     label = KMMessage::quoteHtmlChars( label, true );
01781 
01782     QString dir = mReader->createTempDir( "ktnef-" + QString::number( i ) );
01783     parser.extractFileTo( att->name(), dir );
01784     mReader->mTempFiles.append( dir + QDir::separator() + att->name() );
01785     QString href = "file:" + KURL::encode_string( dir + QDir::separator() + att->name() );
01786 
01787     KMimeType::Ptr mimeType = KMimeType::mimeType( att->mimeTag() );
01788     QString iconName = KGlobal::instance()->iconLoader()->iconPath( mimeType->icon( QString(), false ), KIcon::Desktop );
01789 
01790     htmlWriter()->queue( "<div><a href=\"" + href + "\"><img src=\"" +
01791                           iconName + "\" border=\"0\" style=\"max-width: 100%\">" + label +
01792                           "</a></div><br>" );
01793   }
01794 
01795   if ( !showOnlyOneMimePart() )
01796     htmlWriter()->queue( "</td></tr></table>" );
01797 
01798   return true;
01799 }
01800 
01801   void ObjectTreeParser::writeBodyString( const QCString & bodyString,
01802                                           const QString & fromAddress,
01803                                           const QTextCodec * codec,
01804                                           ProcessResult & result,
01805                                           bool decorate ) {
01806     assert( mReader ); assert( codec );
01807     KMMsgSignatureState inlineSignatureState = result.inlineSignatureState();
01808     KMMsgEncryptionState inlineEncryptionState = result.inlineEncryptionState();
01809     writeBodyStr( bodyString, codec, fromAddress,
01810                   inlineSignatureState, inlineEncryptionState, decorate );
01811     result.setInlineSignatureState( inlineSignatureState );
01812     result.setInlineEncryptionState( inlineEncryptionState );
01813   }
01814 
01815   void ObjectTreeParser::writePartIcon( KMMessagePart * msgPart, int partNum, bool inlineImage ) {
01816     if ( !mReader || !msgPart )
01817       return;
01818 
01819     kdDebug(5006) << "writePartIcon: PartNum: " << partNum << endl;
01820 
01821     QString label = msgPart->fileName();
01822     if( label.isEmpty() )
01823       label = msgPart->name();
01824     if( label.isEmpty() )
01825       label = "unnamed";
01826     label = KMMessage::quoteHtmlChars( label, true );
01827 
01828     QString comment = msgPart->contentDescription();
01829     comment = KMMessage::quoteHtmlChars( comment, true );
01830     if ( label == comment ) comment = QString::null;
01831 
01832     QString fileName = mReader->writeMessagePartToTempFile( msgPart, partNum );
01833 
01834     QString href = fileName.isEmpty() ?
01835       "part://" + QString::number( partNum + 1 ) :
01836       "file:" + KURL::encode_string( fileName ) ;
01837 
01838     QString iconName;
01839     if( inlineImage )
01840       iconName = href;
01841     else {
01842       iconName = msgPart->iconName();
01843       if( iconName.right( 14 ) == "mime_empty.png" ) {
01844         msgPart->magicSetType();
01845         iconName = msgPart->iconName();
01846       }
01847     }
01848 
01849     QCString contentId = msgPart->contentId();
01850     if ( !contentId.isEmpty() ) {
01851       htmlWriter()->embedPart( contentId, href );
01852     }
01853 
01854     if( inlineImage )
01855       // show the filename of the image below the embedded image
01856       htmlWriter()->queue( "<div><a href=\"" + href + "\">"
01857                            "<img src=\"" + iconName + "\" border=\"0\" style=\"max-width: 100%\"></a>"
01858                            "</div>"
01859                            "<div><a href=\"" + href + "\">" + label + "</a>"
01860                            "</div>"
01861                            "<div>" + comment + "</div><br>" );
01862     else
01863       // show the filename next to the image
01864       htmlWriter()->queue( "<div><a href=\"" + href + "\"><img src=\"" +
01865                            iconName + "\" border=\"0\" style=\"max-width: 100%\">" + label +
01866                            "</a></div>"
01867                            "<div>" + comment + "</div><br>" );
01868   }
01869 
01870 #define SIG_FRAME_COL_UNDEF  99
01871 #define SIG_FRAME_COL_RED    -1
01872 #define SIG_FRAME_COL_YELLOW  0
01873 #define SIG_FRAME_COL_GREEN   1
01874 QString ObjectTreeParser::sigStatusToString( const Kleo::CryptoBackend::Protocol* cryptProto,
01875                                         int status_code,
01876                                         GpgME::Signature::Summary summary,
01877                                         int& frameColor,
01878                                         bool& showKeyInfos )
01879 {
01880     // note: At the moment frameColor and showKeyInfos are
01881     //       used for CMS only but not for PGP signatures
01882     // pending(khz): Implement usage of these for PGP sigs as well.
01883     showKeyInfos = true;
01884     QString result;
01885     if( cryptProto ) {
01886         if( cryptProto == Kleo::CryptoBackendFactory::instance()->openpgp() ) {
01887             // process enum according to it's definition to be read in
01888             // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
01889             switch( status_code ) {
01890             case 0: // GPGME_SIG_STAT_NONE
01891                 result = i18n("Error: Signature not verified");
01892                 break;
01893             case 1: // GPGME_SIG_STAT_GOOD
01894                 result = i18n("Good signature");
01895                 break;
01896             case 2: // GPGME_SIG_STAT_BAD
01897                 result = i18n("<b>Bad</b> signature");
01898                 break;
01899             case 3: // GPGME_SIG_STAT_NOKEY
01900                 result = i18n("No public key to verify the signature");
01901                 break;
01902             case 4: // GPGME_SIG_STAT_NOSIG
01903                 result = i18n("No signature found");
01904                 break;
01905             case 5: // GPGME_SIG_STAT_ERROR
01906                 result = i18n("Error verifying the signature");
01907                 break;
01908             case 6: // GPGME_SIG_STAT_DIFF
01909                 result = i18n("Different results for signatures");
01910                 break;
01911             /* PENDING(khz) Verify exact meaning of the following values:
01912             case 7: // GPGME_SIG_STAT_GOOD_EXP
01913                 return i18n("Signature certificate is expired");
01914             break;
01915             case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
01916                 return i18n("One of the certificate's keys is expired");
01917             break;
01918             */
01919             default:
01920                 result = "";   // do *not* return a default text here !
01921                 break;
01922             }
01923         }
01924         else if ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() ) {
01925             // process status bits according to SigStatus_...
01926             // definitions in kdenetwork/libkdenetwork/cryptplug.h
01927 
01928             if( summary == GpgME::Signature::None ) {
01929                 result = i18n("No status information available.");
01930                 frameColor = SIG_FRAME_COL_YELLOW;
01931                 showKeyInfos = false;
01932                 return result;
01933             }
01934 
01935             if( summary & GpgME::Signature::Valid ) {
01936                 result = i18n("Good signature.");
01937                 // Note:
01938                 // Here we are work differently than KMail did before!
01939                 //
01940                 // The GOOD case ( == sig matching and the complete
01941                 // certificate chain was verified and is valid today )
01942                 // by definition does *not* show any key
01943                 // information but just states that things are OK.
01944                 //           (khz, according to LinuxTag 2002 meeting)
01945                 frameColor = SIG_FRAME_COL_GREEN;
01946                 showKeyInfos = false;
01947                 return result;
01948             }
01949 
01950             // we are still there?  OK, let's test the different cases:
01951 
01952             // we assume green, test for yellow or red (in this order!)
01953             frameColor = SIG_FRAME_COL_GREEN;
01954             QString result2;
01955             if( summary & GpgME::Signature::KeyExpired ){
01956                 // still is green!
01957                 result2 += i18n("One key has expired.");
01958             }
01959             if( summary & GpgME::Signature::SigExpired ){
01960                 // and still is green!
01961                 result2 += i18n("The signature has expired.");
01962             }
01963 
01964             // test for yellow:
01965             if( summary & GpgME::Signature::KeyMissing ) {
01966                 result2 += i18n("Unable to verify: key missing.");
01967                 // if the signature certificate is missing
01968                 // we cannot show infos on it
01969                 showKeyInfos = false;
01970                 frameColor = SIG_FRAME_COL_YELLOW;
01971             }
01972             if( summary & GpgME::Signature::CrlMissing ){
01973                 result2 += i18n("CRL not available.");
01974                 frameColor = SIG_FRAME_COL_YELLOW;
01975             }
01976             if( summary & GpgME::Signature::CrlTooOld ){
01977                 result2 += i18n("Available CRL is too old.");
01978                 frameColor = SIG_FRAME_COL_YELLOW;
01979             }
01980             if( summary & GpgME::Signature::BadPolicy ){
01981                 result2 += i18n("A policy was not met.");
01982                 frameColor = SIG_FRAME_COL_YELLOW;
01983             }
01984             if( summary & GpgME::Signature::SysError ){
01985                 result2 += i18n("A system error occurred.");
01986                 // if a system error occurred
01987                 // we cannot trust any information
01988                 // that was given back by the plug-in
01989                 showKeyInfos = false;
01990                 frameColor = SIG_FRAME_COL_YELLOW;
01991             }
01992 
01993             // test for red:
01994             if( summary & GpgME::Signature::KeyRevoked ){
01995                 // this is red!
01996                 result2 += i18n("One key has been revoked.");
01997                 frameColor = SIG_FRAME_COL_RED;
01998             }
01999             if( summary & GpgME::Signature::Red ) {
02000                 if( result2.isEmpty() )
02001                     // Note:
02002                     // Here we are work differently than KMail did before!
02003                     //
02004                     // The BAD case ( == sig *not* matching )
02005                     // by definition does *not* show any key
02006                     // information but just states that things are BAD.
02007                     //
02008                     // The reason for this: In this case ALL information
02009                     // might be falsificated, we can NOT trust the data
02010                     // in the body NOT the signature - so we don't show
02011                     // any key/signature information at all!
02012                     //         (khz, according to LinuxTag 2002 meeting)
02013                     showKeyInfos = false;
02014                 frameColor = SIG_FRAME_COL_RED;
02015             }
02016             else
02017                 result = "";
02018 
02019             if( SIG_FRAME_COL_GREEN == frameColor ) {
02020                 result = i18n("Good signature.");
02021             } else if( SIG_FRAME_COL_RED == frameColor ) {
02022                 result = i18n("<b>Bad</b> signature.");
02023             } else
02024                 result = "";
02025 
02026             if( !result2.isEmpty() ) {
02027                 if( !result.isEmpty() )
02028                     result.append("<br />");
02029                 result.append( result2 );
02030             }
02031         }
02032         /*
02033         // add i18n support for 3rd party plug-ins here:
02034         else if (0 <= cryptPlug->libName().find( "yetanotherpluginname", 0, false )) {
02035 
02036         }
02037         */
02038     }
02039     return result;
02040 }
02041 
02042 
02043 static QString writeSimpleSigstatHeader( const PartMetaData &block )
02044 {
02045   QString html;
02046   html += "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td>";
02047 
02048   if ( block.signClass == "signErr" ) {
02049     html += i18n( "Invalid signature." );
02050   } else if ( block.signClass == "signOkKeyBad" || block.signClass == "signWarn" ) {
02051     html += i18n( "Not enough information to check signature validity." );
02052   } else if ( block.signClass == "signOkKeyOk" ) {
02053     QString addr;
02054     if ( !block.signerMailAddresses.isEmpty() )
02055       addr = block.signerMailAddresses.first();
02056     QString name = addr;
02057     if ( name.isEmpty() )
02058       name = block.signer;
02059     if ( addr.isEmpty() ) {
02060       html += i18n( "Signature is valid." );
02061     } else {
02062       html += i18n( "Signed by <a href=\"mailto:%1\">%2</a>." ).arg( addr, name );
02063     }
02064   } else {
02065     // should not happen
02066     html += i18n( "Unknown signature state" );
02067   }
02068   html += "</td><td align=\"right\">";
02069   html += "<a href=\"kmail:showSignatureDetails\">";
02070   html += i18n( "Show Details" );
02071   html += "</a></td></tr></table>";
02072   return html;
02073 }
02074 
02075 static QString beginVerboseSigstatHeader()
02076 {
02077   return "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td rowspan=\"2\">";
02078 }
02079 
02080 static QString makeShowAuditLogLink( const QString & auditLog ) {
02081   if ( auditLog.isEmpty() )
02082     return i18n("No Audit Log available");
02083 
02084   KURL url;
02085   url.setProtocol( "kmail" );
02086   url.setPath( "showAuditLog" );
02087   url.addQueryItem( "log", auditLog );
02088 
02089   return "<a href=\"" + url.htmlURL() + "\">" + i18n("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + "</a>";
02090 }
02091 
02092 static QString endVerboseSigstatHeader( const PartMetaData & pmd )
02093 {
02094   QString html;
02095   html += "</td><td align=\"right\" valign=\"top\" nowrap=\"nowrap\">";
02096   html += "<a href=\"kmail:hideSignatureDetails\">";
02097   html += i18n( "Hide Details" );
02098   html += "</a></td></tr>";
02099   html += "<tr><td align=\"right\" valign=\"bottom\" nowrap=\"nowrap\">";
02100   html += makeShowAuditLogLink( pmd.auditLog );
02101   html += "</td></tr></table>";
02102   return html;
02103 }
02104 
02105 QString ObjectTreeParser::writeSigstatHeader( PartMetaData & block,
02106                                               const Kleo::CryptoBackend::Protocol * cryptProto,
02107                                               const QString & fromAddress,
02108                                               const QString & filename )
02109 {
02110     const bool isSMIME = cryptProto && ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() );
02111     QString signer = block.signer;
02112 
02113     QString htmlStr, simpleHtmlStr;
02114     QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
02115     QString cellPadding("cellpadding=\"1\"");
02116 
02117     if( block.isEncapsulatedRfc822Message )
02118     {
02119         htmlStr += "<table cellspacing=\"1\" "+cellPadding+" class=\"rfc822\">"
02120             "<tr class=\"rfc822H\"><td dir=\"" + dir + "\">";
02121         if( !filename.isEmpty() )
02122             htmlStr += "<a href=\"" + QString("file:")
02123                      + KURL::encode_string( filename ) + "\">"
02124                      + i18n("Encapsulated message") + "</a>";
02125         else
02126             htmlStr += i18n("Encapsulated message");
02127         htmlStr += "</td></tr><tr class=\"rfc822B\"><td>";
02128     }
02129 
02130     if( block.isEncrypted )
02131     {
02132         htmlStr += "<table cellspacing=\"1\" "+cellPadding+" class=\"encr\">"
02133             "<tr class=\"encrH\"><td dir=\"" + dir + "\">";
02134         if( block.isDecryptable )
02135             htmlStr += i18n("Encrypted message");
02136         else {
02137             htmlStr += i18n("Encrypted message (decryption not possible)");
02138             if( !block.errorText.isEmpty() )
02139                 htmlStr += "<br />" + i18n("Reason: %1").arg( block.errorText );
02140         }
02141         htmlStr += "</td></tr><tr class=\"encrB\"><td>";
02142     }
02143     simpleHtmlStr = htmlStr;
02144 
02145     if( block.isSigned ) {
02146         QStringList& blockAddrs( block.signerMailAddresses );
02147         // note: At the moment frameColor and showKeyInfos are
02148         //       used for CMS only but not for PGP signatures
02149         // pending(khz): Implement usage of these for PGP sigs as well.
02150         int frameColor = SIG_FRAME_COL_UNDEF;
02151         bool showKeyInfos;
02152         bool onlyShowKeyURL = false;
02153         bool cannotCheckSignature = true;
02154         QString statusStr = sigStatusToString( cryptProto,
02155                                                block.status_code,
02156                                                block.sigSummary,
02157                                                frameColor,
02158                                                showKeyInfos );
02159         // if needed fallback to english status text
02160         // that was reported by the plugin
02161         if( statusStr.isEmpty() )
02162             statusStr = block.status;
02163         if( block.technicalProblem )
02164             frameColor = SIG_FRAME_COL_YELLOW;
02165 
02166         switch( frameColor ){
02167             case SIG_FRAME_COL_RED:
02168                 cannotCheckSignature = false;
02169                 break;
02170             case SIG_FRAME_COL_YELLOW:
02171                 cannotCheckSignature = true;
02172                 break;
02173             case SIG_FRAME_COL_GREEN:
02174                 cannotCheckSignature = false;
02175                 break;
02176         }
02177 
02178         // compose the string for displaying the key ID
02179         // either as URL or not linked (for PGP)
02180         // note: Once we can start PGP key manager programs
02181         //       from within KMail we could change this and
02182         //       always show the URL.    (khz, 2002/06/27)
02183         QString startKeyHREF;
02184         if( isSMIME )
02185             startKeyHREF =
02186                 QString("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
02187                 .arg( cryptProto->displayName(),
02188                       cryptProto->name(),
02189                       block.keyId );
02190         QString keyWithWithoutURL
02191             = isSMIME
02192             ? QString("%1%2</a>")
02193                 .arg( startKeyHREF,
02194                       cannotCheckSignature ? i18n("[Details]") : ("0x" + block.keyId) )
02195             : "0x" + QString::fromUtf8( block.keyId );
02196 
02197 
02198         // temporary hack: always show key infos!
02199         showKeyInfos = true;
02200 
02201         // Sorry for using 'black' as null color but .isValid()
02202         // checking with QColor default c'tor did not work for
02203         // some reason.
02204         if( isSMIME && (SIG_FRAME_COL_UNDEF != frameColor) ) {
02205 
02206             // new frame settings for CMS:
02207             // beautify the status string
02208             if( !statusStr.isEmpty() ) {
02209                 statusStr.prepend("<i>");
02210                 statusStr.append( "</i>");
02211             }
02212 
02213             // special color handling: S/MIME uses only green/yellow/red.
02214             switch( frameColor ) {
02215                 case SIG_FRAME_COL_RED:
02216                     block.signClass = "signErr";//"signCMSRed";
02217                     onlyShowKeyURL = true;
02218                     break;
02219                 case SIG_FRAME_COL_YELLOW:
02220                     if( block.technicalProblem )
02221                         block.signClass = "signWarn";
02222                     else
02223                         block.signClass = "signOkKeyBad";//"signCMSYellow";
02224                     break;
02225                 case SIG_FRAME_COL_GREEN:
02226                     block.signClass = "signOkKeyOk";//"signCMSGreen";
02227                     // extra hint for green case
02228                     // that email addresses in DN do not match fromAddress
02229                     QString greenCaseWarning;
02230                     QString msgFrom( KPIM::getEmailAddress(fromAddress) );
02231                     QString certificate;
02232                     if( block.keyId.isEmpty() )
02233                         certificate = i18n("certificate");
02234                     else
02235                         certificate = startKeyHREF + i18n("certificate") + "</a>"; 
02236                     if( !blockAddrs.empty() ){
02237                         if( blockAddrs.grep(
02238                                 msgFrom,
02239                                 false ).isEmpty() ) {
02240                             greenCaseWarning =
02241                                 "<u>" +
02242                                 i18n("Warning:") +
02243                                 "</u> " +
02244                                 i18n("Sender's mail address is not stored "
02245                                      "in the %1 used for signing.").arg(certificate) +
02246                                 "<br />" +
02247                                 i18n("sender: ") +
02248                                 msgFrom +
02249                                 "<br />" +
02250                                 i18n("stored: ");
02251                             // We cannot use Qt's join() function here but
02252                             // have to join the addresses manually to
02253                             // extract the mail addresses (without '<''>')
02254                             // before including it into our string:
02255                             bool bStart = true;
02256                             for(QStringList::ConstIterator it = blockAddrs.begin();
02257                                 it != blockAddrs.end(); ++it ){
02258                                 if( !bStart )
02259                                     greenCaseWarning.append(", <br />&nbsp; &nbsp;");
02260                                 bStart = false;
02261                                 greenCaseWarning.append( KPIM::getEmailAddress(*it) );
02262                             }
02263                         }
02264                     } else {
02265                         greenCaseWarning =
02266                             "<u>" +
02267                             i18n("Warning:") +
02268                             "</u> " +
02269                             i18n("No mail address is stored in the %1 used for signing, "
02270                                  "so we cannot compare it to the sender's address %2.")
02271                             .arg(certificate,msgFrom);
02272                     }
02273                     if( !greenCaseWarning.isEmpty() ) {
02274                         if( !statusStr.isEmpty() )
02275                             statusStr.append("<br />&nbsp;<br />");
02276                         statusStr.append( greenCaseWarning );
02277                     }
02278                     break;
02279             }
02280 
02281             QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
02282                 "class=\"" + block.signClass + "\">"
02283                 "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
02284             htmlStr += frame + beginVerboseSigstatHeader();
02285             simpleHtmlStr += frame;
02286             simpleHtmlStr += writeSimpleSigstatHeader( block );
02287             if( block.technicalProblem ) {
02288                 htmlStr += block.errorText;
02289             }
02290             else if( showKeyInfos ) {
02291                 if( cannotCheckSignature ) {
02292                     htmlStr += i18n( "Not enough information to check "
02293                                      "signature. %1" )
02294                                 .arg( keyWithWithoutURL );
02295                 }
02296                 else {
02297 
02298                     if (block.signer.isEmpty())
02299                         signer = "";
02300                     else {
02301                         if( !blockAddrs.empty() ){
02302                             QString address = KMMessage::encodeMailtoUrl( blockAddrs.first() );
02303                             signer = "<a href=\"mailto:" + address + "\">" + signer + "</a>";
02304                         }
02305                     }
02306 
02307                     if( block.keyId.isEmpty() ) {
02308                         if( signer.isEmpty() || onlyShowKeyURL )
02309                             htmlStr += i18n( "Message was signed with unknown key." );
02310                         else
02311                             htmlStr += i18n( "Message was signed by %1." )
02312                                     .arg( signer );
02313                     } else {
02314                         QDateTime created = block.creationTime;
02315                         if( created.isValid() ) {
02316                             if( signer.isEmpty() ) {
02317                                 if( onlyShowKeyURL )
02318                                     htmlStr += i18n( "Message was signed with key %1." )
02319                                                 .arg( keyWithWithoutURL );
02320                                 else
02321                                     htmlStr += i18n( "Message was signed on %1 with key %2." )
02322                                                 .arg( KGlobal::locale()->formatDateTime( created ),
02323                                                       keyWithWithoutURL );
02324                             }
02325                             else {
02326                                 if( onlyShowKeyURL )
02327                                     htmlStr += i18n( "Message was signed with key %1." )
02328                                             .arg( keyWithWithoutURL );
02329                                 else
02330                                     htmlStr += i18n( "Message was signed by %3 on %1 with key %2" )
02331                                             .arg( KGlobal::locale()->formatDateTime( created ),
02332                                                   keyWithWithoutURL,
02333                                                   signer );
02334                             }
02335                         }
02336                         else {
02337                             if( signer.isEmpty() || onlyShowKeyURL )
02338                                 htmlStr += i18n( "Message was signed with key %1." )
02339                                         .arg( keyWithWithoutURL );
02340                             else
02341                                 htmlStr += i18n( "Message was signed by %2 with key %1." )
02342                                         .arg( keyWithWithoutURL,
02343                                               signer );
02344                         }
02345                     }
02346                 }
02347                 htmlStr += "<br />";
02348                 if( !statusStr.isEmpty() ) {
02349                     htmlStr += "&nbsp;<br />";
02350                     htmlStr += i18n( "Status: " );
02351                     htmlStr += statusStr;
02352                 }
02353             } else {
02354                 htmlStr += statusStr;
02355             }
02356             frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
02357             htmlStr += endVerboseSigstatHeader( block ) + frame;
02358             simpleHtmlStr += frame;
02359 
02360         } else {
02361 
02362             // old frame settings for PGP:
02363 
02364             if( block.signer.isEmpty() || block.technicalProblem ) {
02365                 block.signClass = "signWarn";
02366                 QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
02367                     "class=\"" + block.signClass + "\">"
02368                     "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
02369                 htmlStr += frame + beginVerboseSigstatHeader();
02370                 simpleHtmlStr += frame;
02371                 simpleHtmlStr += writeSimpleSigstatHeader( block );
02372                 if( block.technicalProblem ) {
02373                     htmlStr += block.errorText;
02374                 }
02375                 else {
02376                   if( !block.keyId.isEmpty() ) {
02377                     QDateTime created = block.creationTime;
02378                     if ( created.isValid() )
02379                         htmlStr += i18n( "Message was signed on %1 with unknown key %2." )
02380                                 .arg( KGlobal::locale()->formatDateTime( created ),
02381                                       keyWithWithoutURL );
02382                     else
02383                         htmlStr += i18n( "Message was signed with unknown key %1." )
02384                                 .arg( keyWithWithoutURL );
02385                   }
02386                   else
02387                     htmlStr += i18n( "Message was signed with unknown key." );
02388                   htmlStr += "<br />";
02389                   htmlStr += i18n( "The validity of the signature cannot be "
02390                                    "verified." );
02391                   if( !statusStr.isEmpty() ) {
02392                     htmlStr += "<br />";
02393                     htmlStr += i18n( "Status: " );
02394                     htmlStr += "<i>";
02395                     htmlStr += statusStr;
02396                     htmlStr += "</i>";
02397                   }
02398                 }
02399                 frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
02400                 htmlStr += endVerboseSigstatHeader( block ) + frame;
02401                 simpleHtmlStr += frame;
02402             }
02403             else
02404             {
02405                 // HTMLize the signer's user id and create mailto: link
02406                 signer = KMMessage::quoteHtmlChars( signer, true );
02407                 signer = "<a href=\"mailto:" + signer + "\">" + signer + "</a>";
02408 
02409                 if (block.isGoodSignature) {
02410                     if( block.keyTrust < Kpgp::KPGP_VALIDITY_MARGINAL )
02411                         block.signClass = "signOkKeyBad";
02412                     else
02413                         block.signClass = "signOkKeyOk";
02414                     QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
02415                         "class=\"" + block.signClass + "\">"
02416                         "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
02417                     htmlStr += frame + beginVerboseSigstatHeader();
02418                     simpleHtmlStr += frame;
02419                     simpleHtmlStr += writeSimpleSigstatHeader( block );
02420                     if( !block.keyId.isEmpty() )
02421                         htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." )
02422                                    .arg( keyWithWithoutURL,
02423                                          signer );
02424                     else
02425                         htmlStr += i18n( "Message was signed by %1." ).arg( signer );
02426                     htmlStr += "<br />";
02427 
02428                     switch( block.keyTrust )
02429                     {
02430                         case Kpgp::KPGP_VALIDITY_UNKNOWN:
02431                         htmlStr += i18n( "The signature is valid, but the key's "
02432                                 "validity is unknown." );
02433                         break;
02434                         case Kpgp::KPGP_VALIDITY_MARGINAL:
02435                         htmlStr += i18n( "The signature is valid and the key is "
02436                                 "marginally trusted." );
02437                         break;
02438                         case Kpgp::KPGP_VALIDITY_FULL:
02439                         htmlStr += i18n( "The signature is valid and the key is "
02440                                 "fully trusted." );
02441                         break;
02442                         case Kpgp::KPGP_VALIDITY_ULTIMATE:
02443                         htmlStr += i18n( "The signature is valid and the key is "
02444                                 "ultimately trusted." );
02445                         break;
02446                         default:
02447                         htmlStr += i18n( "The signature is valid, but the key is "
02448                                 "untrusted." );
02449                     }
02450                     frame = "</td></tr>"
02451                         "<tr class=\"" + block.signClass + "B\"><td>";
02452                     htmlStr += endVerboseSigstatHeader( block ) + frame;
02453                     simpleHtmlStr += frame;
02454                 }
02455                 else
02456                 {
02457                     block.signClass = "signErr";
02458                     QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
02459                         "class=\"" + block.signClass + "\">"
02460                         "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
02461                     htmlStr += frame + beginVerboseSigstatHeader();
02462                     simpleHtmlStr += frame;
02463                     simpleHtmlStr += writeSimpleSigstatHeader( block );
02464                     if( !block.keyId.isEmpty() )
02465                         htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." )
02466                         .arg( keyWithWithoutURL,
02467                               signer );
02468                     else
02469                         htmlStr += i18n( "Message was signed by %1." ).arg( signer );
02470                     htmlStr += "<br />";
02471                     htmlStr += i18n("Warning: The signature is bad.");
02472                     frame = "</td></tr>"
02473                         "<tr class=\"" + block.signClass + "B\"><td>";
02474                     htmlStr += endVerboseSigstatHeader( block ) + frame;
02475                     simpleHtmlStr += frame;
02476                 }
02477             }
02478         }
02479     }
02480 
02481     if ( mReader->showSignatureDetails() )
02482       return htmlStr;
02483     return simpleHtmlStr;
02484 }
02485 
02486 QString ObjectTreeParser::writeSigstatFooter( PartMetaData& block )
02487 {
02488     QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
02489 
02490     QString htmlStr;
02491 
02492     if (block.isSigned) {
02493         htmlStr += "</td></tr><tr class=\"" + block.signClass + "H\">";
02494         htmlStr += "<td dir=\"" + dir + "\">" +
02495             i18n( "End of signed message" ) +
02496             "</td></tr></table>";
02497     }
02498 
02499     if (block.isEncrypted) {
02500         htmlStr += "</td></tr><tr class=\"encrH\"><td dir=\"" + dir + "\">" +
02501                 i18n( "End of encrypted message" ) +
02502             "</td></tr></table>";
02503     }
02504 
02505     if( block.isEncapsulatedRfc822Message )
02506     {
02507         htmlStr += "</td></tr><tr class=\"rfc822H\"><td dir=\"" + dir + "\">" +
02508             i18n( "End of encapsulated message" ) +
02509             "</td></tr></table>";
02510     }
02511 
02512     return htmlStr;
02513 }
02514 
02515 //-----------------------------------------------------------------------------
02516 void ObjectTreeParser::writeBodyStr( const QCString& aStr, const QTextCodec *aCodec,
02517                                 const QString& fromAddress )
02518 {
02519   KMMsgSignatureState dummy1;
02520   KMMsgEncryptionState dummy2;
02521   writeBodyStr( aStr, aCodec, fromAddress, dummy1, dummy2, false );
02522 }
02523 
02524 //-----------------------------------------------------------------------------
02525 void ObjectTreeParser::writeBodyStr( const QCString& aStr, const QTextCodec *aCodec,
02526                                 const QString& fromAddress,
02527                                 KMMsgSignatureState&  inlineSignatureState,
02528                                 KMMsgEncryptionState& inlineEncryptionState,
02529                                 bool decorate )
02530 {
02531   bool goodSignature = false;
02532   Kpgp::Module* pgp = Kpgp::Module::getKpgp();
02533   assert(pgp != 0);
02534   bool isPgpMessage = false; // true if the message contains at least one
02535                              // PGP MESSAGE or one PGP SIGNED MESSAGE block
02536   QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
02537   QString headerStr = QString("<div dir=\"%1\">").arg(dir);
02538 
02539   inlineSignatureState  = KMMsgNotSigned;
02540   inlineEncryptionState = KMMsgNotEncrypted;
02541   QPtrList<Kpgp::Block> pgpBlocks;
02542   QStrList nonPgpBlocks;
02543   if( Kpgp::Module::prepareMessageForDecryption( aStr, pgpBlocks, nonPgpBlocks ) )
02544   {
02545       bool isEncrypted = false, isSigned = false;
02546       bool fullySignedOrEncrypted = true;
02547       bool firstNonPgpBlock = true;
02548       bool couldDecrypt = false;
02549       QString signer;
02550       QCString keyId;
02551       QString decryptionError;
02552       Kpgp::Validity keyTrust = Kpgp::KPGP_VALIDITY_FULL;
02553 
02554       QPtrListIterator<Kpgp::Block> pbit( pgpBlocks );
02555 
02556       QStrListIterator npbit( nonPgpBlocks );
02557 
02558       QString htmlStr;
02559       for( ; *pbit != 0; ++pbit, ++npbit )
02560       {
02561           // insert the next Non-OpenPGP block
02562           QCString str( *npbit );
02563           if( !str.isEmpty() ) {
02564             htmlStr += quotedHTML( aCodec->toUnicode( str ), decorate );
02565             kdDebug( 5006 ) << "Non-empty Non-OpenPGP block found: '" << str
02566                             << "'" << endl;
02567             // treat messages with empty lines before the first clearsigned
02568             // block as fully signed/encrypted
02569             if( firstNonPgpBlock ) {
02570               // check whether str only consists of \n
02571               for( QCString::ConstIterator c = str.begin(); *c; ++c ) {
02572                 if( *c != '\n' ) {
02573                   fullySignedOrEncrypted = false;
02574                   break;
02575                 }
02576               }
02577             }
02578             else {
02579               fullySignedOrEncrypted = false;
02580             }
02581           }
02582           firstNonPgpBlock = false;
02583 
02584           //htmlStr += "<br>";
02585 
02586           Kpgp::Block* block = *pbit;
02587           if( ( block->type() == Kpgp::PgpMessageBlock &&
02588                 // ### Workaround for bug 56693
02589                 !kmkernel->contextMenuShown() ) ||
02590               ( block->type() == Kpgp::ClearsignedBlock ) )
02591           {
02592               isPgpMessage = true;
02593               if( block->type() == Kpgp::PgpMessageBlock )
02594               {
02595                 if ( mReader )
02596                   emit mReader->noDrag();
02597                 // try to decrypt this OpenPGP block
02598                 couldDecrypt = block->decrypt();
02599                 isEncrypted = block->isEncrypted();
02600                 if (!couldDecrypt) {
02601                   decryptionError = pgp->lastErrorMsg();
02602                 }
02603               }
02604               else
02605               {
02606                   // try to verify this OpenPGP block
02607                   block->verify();
02608               }
02609 
02610               isSigned = block->isSigned();
02611               if( isSigned )
02612               {
02613                   keyId = block->signatureKeyId();
02614                   signer = block->signatureUserId();
02615                   if( !signer.isEmpty() )
02616                   {
02617                       goodSignature = block->goodSignature();
02618 
02619                       if( !keyId.isEmpty() ) {
02620                         keyTrust = pgp->keyTrust( keyId );
02621                         Kpgp::Key* key = pgp->publicKey( keyId );
02622                         if ( key ) {
02623                           // Use the user ID from the key because this one
02624                           // is charset safe.
02625                           signer = key->primaryUserID();
02626                         }
02627                       }
02628                       else
02629                         // This is needed for the PGP 6 support because PGP 6 doesn't
02630                         // print the key id of the signing key if the key is known.
02631                         keyTrust = pgp->keyTrust( signer );
02632                   }
02633               }
02634 
02635               if( isSigned )
02636                 inlineSignatureState = KMMsgPartiallySigned;
02637               if( isEncrypted )
02638                 inlineEncryptionState = KMMsgPartiallyEncrypted;
02639 
02640               PartMetaData messagePart;
02641 
02642               messagePart.isSigned = isSigned;
02643               messagePart.technicalProblem = false;
02644               messagePart.isGoodSignature = goodSignature;
02645               messagePart.isEncrypted = isEncrypted;
02646               messagePart.isDecryptable = couldDecrypt;
02647               messagePart.decryptionError = decryptionError;
02648               messagePart.signer = signer;
02649               messagePart.keyId = keyId;
02650               messagePart.keyTrust = keyTrust;
02651 
02652               htmlStr += writeSigstatHeader( messagePart, 0, fromAddress );
02653 
02654               htmlStr += quotedHTML( aCodec->toUnicode( block->text() ), decorate );
02655               htmlStr += writeSigstatFooter( messagePart );
02656           }
02657           else // block is neither message block nor clearsigned block
02658             htmlStr += quotedHTML( aCodec->toUnicode( block->text() ),
02659                                    decorate );
02660       }
02661 
02662       // add the last Non-OpenPGP block
02663       QCString str( nonPgpBlocks.last() );
02664       if( !str.isEmpty() ) {
02665         htmlStr += quotedHTML( aCodec->toUnicode( str ), decorate );
02666         // Even if the trailing Non-OpenPGP block isn't empty we still
02667         // consider the message part fully signed/encrypted because else
02668         // all inline signed mailing list messages would only be partially
02669         // signed because of the footer which is often added by the mailing
02670         // list software. IK, 2003-02-15
02671       }
02672       if( fullySignedOrEncrypted ) {
02673         if( inlineSignatureState == KMMsgPartiallySigned )
02674           inlineSignatureState = KMMsgFullySigned;
02675         if( inlineEncryptionState == KMMsgPartiallyEncrypted )
02676           inlineEncryptionState = KMMsgFullyEncrypted;
02677       }
02678       htmlWriter()->queue( htmlStr );
02679   }
02680   else
02681     htmlWriter()->queue( quotedHTML( aCodec->toUnicode( aStr ), decorate ) );
02682 }
02683 
02684 
02685 QString ObjectTreeParser::quotedHTML( const QString& s, bool decorate )
02686 {
02687   assert( mReader );
02688   assert( cssHelper() );
02689 
02690   int convertFlags = LinkLocator::PreserveSpaces;
02691   if ( decorate && GlobalSettings::self()->showEmoticons() ) {
02692     convertFlags |= LinkLocator::ReplaceSmileys;
02693   }
02694   QString htmlStr;
02695   const QString normalStartTag = cssHelper()->nonQuotedFontTag();
02696   QString quoteFontTag[3];
02697   QString deepQuoteFontTag[3];
02698   for ( int i = 0 ; i < 3 ; ++i ) {
02699     quoteFontTag[i] = cssHelper()->quoteFontTag( i );
02700     deepQuoteFontTag[i] = cssHelper()->quoteFontTag( i+3 );
02701   }
02702   const QString normalEndTag = "</div>";
02703   const QString quoteEnd = "</div>";
02704 
02705   unsigned int pos, beg;
02706   const unsigned int length = s.length();
02707 
02708   // skip leading empty lines
02709   for ( pos = 0; pos < length && s[pos] <= ' '; pos++ );
02710   while (pos > 0 && (s[pos-1] == ' ' || s[pos-1] == '\t')) pos--;
02711   beg = pos;
02712 
02713   int currQuoteLevel = -2; // -2 == no previous lines
02714   bool curHidden = false; // no hide any block
02715 
02716   while (beg<length)
02717   {
02718     QString line;
02719 
02720     /* search next occurrence of '\n' */
02721     pos = s.find('\n', beg, FALSE);
02722     if (pos == (unsigned int)(-1))
02723         pos = length;
02724 
02725     line = s.mid(beg,pos-beg);
02726     beg = pos+1;
02727 
02728     /* calculate line's current quoting depth */
02729     int actQuoteLevel = -1;
02730 
02731     if ( GlobalSettings::self()->showExpandQuotesMark() )
02732     {
02733       // Cache Icons
02734       if ( mCollapseIcon.isEmpty() ) {
02735         mCollapseIcon= LinkLocator::pngToDataUrl(
02736             KGlobal::instance()->iconLoader()->iconPath( "quotecollapse",0 ));
02737       }
02738       if ( mExpandIcon.isEmpty() )
02739         mExpandIcon= LinkLocator::pngToDataUrl(
02740             KGlobal::instance()->iconLoader()->iconPath( "quoteexpand",0 ));
02741     }
02742 
02743     for (unsigned int p=0; p<line.length(); p++) {
02744       switch (line[p].latin1()) {
02745         case '>':
02746         case '|':
02747           actQuoteLevel++;
02748           break;
02749         case ' ':  // spaces and tabs are allowed between the quote markers
02750         case '\t':
02751         case '\r':
02752           break;
02753         default:  // stop quoting depth calculation
02754           p = line.length();
02755           break;
02756       }
02757     } /* for() */
02758 
02759     bool actHidden = false;
02760     QString textExpand;
02761 
02762     // This quoted line needs be hiden
02763     if (GlobalSettings::self()->showExpandQuotesMark() && mReader->mLevelQuote >= 0
02764         && mReader->mLevelQuote <= ( actQuoteLevel ) )
02765       actHidden = true;
02766 
02767     if ( actQuoteLevel != currQuoteLevel ) {
02768       /* finish last quotelevel */
02769       if (currQuoteLevel == -1)
02770         htmlStr.append( normalEndTag );
02771       else if ( currQuoteLevel >= 0 && !curHidden )
02772         htmlStr.append( quoteEnd );
02773 
02774       /* start new quotelevel */
02775       if (actQuoteLevel == -1)
02776         htmlStr += normalStartTag;
02777       else
02778       {
02779         if ( GlobalSettings::self()->showExpandQuotesMark() )
02780         {
02781           if (  actHidden )
02782           {
02783             //only show the QuoteMark when is the first line of the level hidden
02784             if ( !curHidden )
02785             {
02786               //Expand all quotes
02787               htmlStr += "<div class=\"quotelevelmark\" >" ;
02788               htmlStr += QString( "<a href=\"kmail:levelquote?%1 \">"
02789                   "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
02790                 .arg(-1)
02791                 .arg( mExpandIcon );
02792               htmlStr += "</div><br/>";
02793               htmlStr += quoteEnd;
02794             }
02795           }else {
02796             htmlStr += "<div class=\"quotelevelmark\" >" ;
02797             htmlStr += QString( "<a href=\"kmail:levelquote?%1 \">"
02798                 "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
02799               .arg(actQuoteLevel)
02800               .arg( mCollapseIcon);
02801             htmlStr += "</div>";
02802             if ( actQuoteLevel < 3 )
02803               htmlStr += quoteFontTag[actQuoteLevel];
02804             else
02805               htmlStr += deepQuoteFontTag[actQuoteLevel%3];
02806           }
02807         } else
02808             if ( actQuoteLevel < 3 )
02809               htmlStr += quoteFontTag[actQuoteLevel];
02810             else
02811               htmlStr += deepQuoteFontTag[actQuoteLevel%3];
02812       }
02813       currQuoteLevel = actQuoteLevel;
02814     }
02815     curHidden = actHidden;
02816 
02817 
02818     if ( !actHidden )
02819     {
02820       // don't write empty <div ...></div> blocks (they have zero height)
02821       // ignore ^M DOS linebreaks
02822       if( !line.replace('\015', "").isEmpty() )
02823       {
02824          htmlStr +=QString( "<div dir=\"%1\">" ).arg( line.isRightToLeft() ? "rtl":"ltr" );
02825          htmlStr += LinkLocator::convertToHtml( line, convertFlags );
02826          htmlStr += QString( "</div>" );
02827       }
02828       else
02829         htmlStr += "<br>";
02830     }
02831   } /* while() */
02832 
02833   /* really finish the last quotelevel */
02834   if (currQuoteLevel == -1)
02835      htmlStr.append( normalEndTag );
02836   else
02837      htmlStr.append( quoteEnd );
02838 
02839   //kdDebug(5006) << "KMReaderWin::quotedHTML:\n"
02840   //              << "========================================\n"
02841   //              << htmlStr
02842   //              << "\n======================================\n";
02843   return htmlStr;
02844 }
02845 
02846 
02847 
02848   const QTextCodec * ObjectTreeParser::codecFor( partNode * node ) const {
02849     assert( node );
02850     if ( mReader && mReader->overrideCodec() )
02851       return mReader->overrideCodec();
02852     return node->msgPart().codec();
02853   }
02854 
02855 #ifdef MARCS_DEBUG
02856   void ObjectTreeParser::dumpToFile( const char * filename, const char * start,
02857                                      size_t len ) {
02858     assert( filename );
02859 
02860     QFile f( filename );
02861     if ( f.open( IO_WriteOnly ) ) {
02862       if ( start ) {
02863         QDataStream ds( &f );
02864         ds.writeRawBytes( start, len );
02865       }
02866       f.close();  // If data is 0 we just create a zero length file.
02867     }
02868   }
02869 #endif // !NDEBUG
02870 
02871 
02872 } // namespace KMail