• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

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 // my header file
00034 #include "objecttreeparser.h"
00035 
00036 // other KMail headers
00037 #include "kmkernel.h"
00038 #include "kmreaderwin.h"
00039 #include "partNode.h"
00040 #include <kpimutils/email.h>
00041 #include "partmetadata.h"
00042 #include "attachmentstrategy.h"
00043 #include "interfaces/htmlwriter.h"
00044 #include "htmlstatusbar.h"
00045 #include "csshelper.h"
00046 #include "bodypartformatter.h"
00047 #include "bodypartformatterfactory.h"
00048 #include "partnodebodypart.h"
00049 #include "interfaces/bodypartformatter.h"
00050 #include "globalsettings.h"
00051 #include "util.h"
00052 #include "kleojobexecutor.h"
00053 
00054 // other module headers
00055 #include <mimelib/enum.h>
00056 #include <mimelib/bodypart.h>
00057 #include <mimelib/string.h>
00058 #include <mimelib/text.h>
00059 
00060 #include <kleo/specialjob.h>
00061 #include <kleo/cryptobackendfactory.h>
00062 #include <kleo/decryptverifyjob.h>
00063 #include <kleo/verifydetachedjob.h>
00064 #include <kleo/verifyopaquejob.h>
00065 #include <kleo/keylistjob.h>
00066 #include <kleo/importjob.h>
00067 #include <kleo/dn.h>
00068 
00069 #include <gpgme++/importresult.h>
00070 #include <gpgme++/decryptionresult.h>
00071 #include <gpgme++/key.h>
00072 #include <gpgme++/keylistresult.h>
00073 #include <gpgme.h>
00074 
00075 #include <libkpgp/kpgpblock.h>
00076 #include <libkpgp/kpgp.h>
00077 #include <kpimutils/linklocator.h>
00078 using KPIMUtils::LinkLocator;
00079 
00080 #include <ktnef/ktnefparser.h>
00081 #include <ktnef/ktnefmessage.h>
00082 #include <ktnef/ktnefattach.h>
00083 
00084 // other KDE headers
00085 #include <kdebug.h>
00086 #include <klocale.h>
00087 #include <kmimetype.h>
00088 #include <kglobal.h>
00089 #include <khtml_part.h>
00090 #include <ktemporaryfile.h>
00091 #include <kstandarddirs.h>
00092 #include <kmessagebox.h>
00093 #include <kiconloader.h>
00094 #include <kcodecs.h>
00095 #include <kconfiggroup.h>
00096 #include <kstyle.h>
00097 
00098 // other Qt headers
00099 #include <QApplication>
00100 #include <QDir>
00101 #include <QFile>
00102 #include <QTextCodec>
00103 #include <QByteArray>
00104 #include <QBuffer>
00105 #include <QPixmap>
00106 #include <QPainter>
00107 #include <QPointer>
00108 
00109 // other headers
00110 #include <sys/stat.h>
00111 #include <sys/types.h>
00112 #include <unistd.h>
00113 #include <memory>
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       kDebug() << "\n     ----->  Inserting items into MimePartTree";
00216       newNode->fillMimePartTree( startNode.mimePartTreeItem(), 0,
00217                                  QString(), QString(), QString(), 0,
00218                                  append );
00219       kDebug() << "\n     <-----  Finished inserting items into MimePartTree";
00220     } else {
00221       kDebug() << "\n     ------  Sorry, node.mimePartTreeItem() returns ZERO so"
00222                << "\n                    we cannot insert new lines into MimePartTree. :-(\n";
00223     }
00224     kDebug() << "\n     ----->  Now parsing the MimePartTree";
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     kDebug() << "\n     <-----  Finished parsing the MimePartTree in insertAndParseNewChildNode()";
00232   }
00233 
00234 
00235 //-----------------------------------------------------------------------------
00236 
00237   void ObjectTreeParser::parseObjectTree( partNode * node ) {
00238     kDebug() << (node ? "node OK, " : "no node, ")
00239              << "showOnlyOneMimePart: " << (showOnlyOneMimePart() ? "TRUE" : "FALSE");
00240 
00241     if ( !node )
00242       return;
00243 
00244     // reset "processed" flags for...
00245     if ( showOnlyOneMimePart() ) {
00246       // ... this node and all descendants
00247       node->setProcessed( false, false );
00248       if ( partNode * child = node->firstChild() )
00249         child->setProcessed( false, true );
00250     } else if ( mReader && !node->parentNode() ) {
00251       // ...this node and all it's siblings and descendants
00252       node->setProcessed( false, true );
00253     }
00254 
00255     for ( ; node ; node = node->nextSibling() ) {
00256       if ( node->processed() )
00257         continue;
00258 
00259       ProcessResult processResult;
00260 
00261       if ( mReader )
00262         htmlWriter()->queue( QString::fromLatin1("<a name=\"att%1\"/>").arg( node->nodeId() ) );
00263       if ( const Interface::BodyPartFormatter * formatter
00264            = BodyPartFormatterFactory::instance()->createFor( node->typeString(), node->subTypeString() ) ) {
00265         PartNodeBodyPart part( *node, codecFor( node ) );
00266         // Set the default display strategy for this body part relying on the
00267         // identity of KMail::Interface::BodyPart::Display and AttachmentStrategy::Display
00268         part.setDefaultDisplay( (KMail::Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay( node ) );
00269         const Interface::BodyPartFormatter::Result result = formatter->format( &part, htmlWriter() );
00270         if ( mReader && node->bodyPartMemento() )
00271           if ( Interface::Observable * obs = node->bodyPartMemento()->asObservable() )
00272             obs->attach( mReader );
00273         switch ( result ) {
00274         case Interface::BodyPartFormatter::AsIcon:
00275           processResult.setNeverDisplayInline( true );
00276           // fall through:
00277         case Interface::BodyPartFormatter::Failed:
00278           defaultHandling( node, processResult );
00279           break;
00280         case Interface::BodyPartFormatter::Ok:
00281         case Interface::BodyPartFormatter::NeedContent:
00282           // FIXME: incomplete content handling
00283           ;
00284         }
00285       } else {
00286         const BodyPartFormatter * bpf
00287           = BodyPartFormatter::createFor( node->type(), node->subType() );
00288         kFatal( !bpf, 5006 ) <<"THIS SHOULD NO LONGER HAPPEN ("
00289                               << node->typeString() << '/' << node->subTypeString() << ')';
00290 
00291         if ( bpf && !bpf->process( this, node, processResult ) )
00292           defaultHandling( node, processResult );
00293       }
00294       node->setProcessed( true, false );
00295 
00296       // adjust signed/encrypted flags if inline PGP was found
00297       processResult.adjustCryptoStatesOfNode( node );
00298 
00299       if ( showOnlyOneMimePart() )
00300         break;
00301     }
00302   }
00303 
00304   void ObjectTreeParser::defaultHandling( partNode * node, ProcessResult & result ) {
00305     // ### (mmutz) default handling should go into the respective
00306     // ### bodypartformatters.
00307     if ( !mReader )
00308       return;
00309     if ( attachmentStrategy() == AttachmentStrategy::hidden() &&
00310          !showOnlyOneMimePart() &&
00311          node->parentNode() /* message is not an attachment */ )
00312       return;
00313 
00314     bool asIcon = true;
00315     if ( showOnlyOneMimePart() )
00316       // ### (mmutz) this is wrong! If I click on an image part, I
00317       // want the equivalent of "view...", except for the extra
00318       // window!
00319       asIcon = !node->hasContentDispositionInline();
00320     else if ( !result.neverDisplayInline() )
00321       if ( const AttachmentStrategy * as = attachmentStrategy() )
00322         asIcon = as->defaultDisplay( node ) == AttachmentStrategy::AsIcon;
00323     // neither image nor text -> show as icon
00324     if ( !result.isImage()
00325          && node->type() != DwMime::kTypeText )
00326       asIcon = true;
00327     // if the image is not complete do not try to show it inline
00328     if ( result.isImage() && !node->msgPart().isComplete() )
00329       asIcon = true;
00330     if ( asIcon ) {
00331       if ( attachmentStrategy() != AttachmentStrategy::hidden()
00332            || showOnlyOneMimePart() )
00333         writePartIcon( &node->msgPart(), node->nodeId() );
00334     } else if ( result.isImage() )
00335       writePartIcon( &node->msgPart(), node->nodeId(), true );
00336     else
00337       writeBodyString( node->msgPart().bodyDecoded(),
00338                        node->trueFromAddress(),
00339                        codecFor( node ), result, false );
00340     // end of ###
00341   }
00342 
00343   void ProcessResult::adjustCryptoStatesOfNode( partNode * node ) const {
00344     if ( ( inlineSignatureState()  != KMMsgNotSigned ) ||
00345          ( inlineEncryptionState() != KMMsgNotEncrypted ) ) {
00346       node->setSignatureState( inlineSignatureState() );
00347       node->setEncryptionState( inlineEncryptionState() );
00348     }
00349   }
00350 
00354 
00355   static int signatureToStatus( const GpgME::Signature &sig )
00356   {
00357     switch ( sig.status().code() ) {
00358       case GPG_ERR_NO_ERROR:
00359         return GPGME_SIG_STAT_GOOD;
00360       case GPG_ERR_BAD_SIGNATURE:
00361         return GPGME_SIG_STAT_BAD;
00362       case GPG_ERR_NO_PUBKEY:
00363         return GPGME_SIG_STAT_NOKEY;
00364       case GPG_ERR_NO_DATA:
00365         return GPGME_SIG_STAT_NOSIG;
00366       case GPG_ERR_SIG_EXPIRED:
00367         return GPGME_SIG_STAT_GOOD_EXP;
00368       case GPG_ERR_KEY_EXPIRED:
00369         return GPGME_SIG_STAT_GOOD_EXPKEY;
00370       default:
00371         return GPGME_SIG_STAT_ERROR;
00372     }
00373   }
00374 
00375   bool ObjectTreeParser::writeOpaqueOrMultipartSignedData( partNode* data,
00376                                                       partNode& sign,
00377                                                       const QString& fromAddress,
00378                                                       bool doCheck,
00379                                                       QByteArray* cleartextData,
00380                                                       std::vector<GpgME::Signature> paramSignatures,
00381                                                       bool hideErrors )
00382   {
00383     bool bIsOpaqueSigned = false;
00384     enum { NO_PLUGIN, NOT_INITIALIZED, CANT_VERIFY_SIGNATURES }
00385       cryptPlugError = NO_PLUGIN;
00386 
00387     const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
00388 
00389     QString cryptPlugLibName;
00390     QString cryptPlugDisplayName;
00391     if ( cryptProto ) {
00392       cryptPlugLibName = cryptProto->name();
00393       cryptPlugDisplayName = cryptProto->displayName();
00394     }
00395 
00396 #ifndef NDEBUG
00397     if ( !doCheck )
00398       kDebug() << "showing OpenPGP (Encrypted+Signed) data";
00399     else
00400       if ( data )
00401         kDebug() << "processing Multipart Signed data";
00402       else
00403         kDebug() << "processing Opaque Signed data";
00404 #endif
00405 
00406     if ( doCheck && cryptProto ) {
00407       kDebug() << "going to call CRYPTPLUG" << cryptPlugLibName;
00408     }
00409 
00410     QByteArray cleartext;
00411     QByteArray signaturetext;
00412 
00413     if ( doCheck && cryptProto ) {
00414       if ( data ) {
00415         cleartext = KMail::Util::ByteArray( data->dwPart()->AsString() );
00416 
00417         dumpToFile( "dat_01_reader_signedtext_before_canonicalization",
00418                     cleartext.data(), cleartext.length() );
00419 
00420         // replace simple LFs by CRLSs
00421         // according to RfC 2633, 3.1.1 Canonicalization
00422         kDebug() <<"Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)";
00423         cleartext = Util::lf2crlf( cleartext );
00424         kDebug() <<"                                                       done.";
00425       }
00426 
00427       dumpToFile( "dat_02_reader_signedtext_after_canonicalization",
00428                   cleartext.data(), cleartext.length() );
00429 
00430       signaturetext = sign.msgPart().bodyDecodedBinary();
00431       dumpToFile( "dat_03_reader.sig", signaturetext.data(),
00432                   signaturetext.size() );
00433     }
00434 
00435     std::vector<GpgME::Signature> signatures;
00436     if ( doCheck )
00437       signatures = paramSignatures;
00438 
00439     PartMetaData messagePart;
00440     messagePart.isSigned = true;
00441     messagePart.technicalProblem = ( cryptProto == 0 );
00442     messagePart.isGoodSignature = false;
00443     messagePart.isEncrypted = false;
00444     messagePart.isDecryptable = false;
00445     messagePart.keyTrust = Kpgp::KPGP_VALIDITY_UNKNOWN;
00446     messagePart.status = i18n("Wrong Crypto Plug-In.");
00447     messagePart.status_code = GPGME_SIG_STAT_NONE;
00448 
00449     if ( doCheck && cryptProto ) {
00450       GpgME::VerificationResult result;
00451       if ( data ) { // detached
00452         if ( Kleo::VerifyDetachedJob * const job = cryptProto->verifyDetachedJob() ) {
00453           KleoJobExecutor executor;
00454           result = executor.exec( job, signaturetext, cleartext );
00455           messagePart.auditLog = executor.auditLogAsHtml();
00456         } else {
00457           cryptPlugError = CANT_VERIFY_SIGNATURES;
00458         }
00459       } else { // opaque
00460         if ( Kleo::VerifyOpaqueJob * const job = cryptProto->verifyOpaqueJob() ) {
00461           KleoJobExecutor executor;
00462           result = executor.exec( job, signaturetext, cleartext );
00463           messagePart.auditLog = executor.auditLogAsHtml();
00464         } else {
00465           cryptPlugError = CANT_VERIFY_SIGNATURES;
00466         }
00467       }
00468       signatures = result.signatures();
00469     }
00470 
00471     if ( doCheck )
00472       kDebug() << "returned from CRYPTPLUG";
00473 
00474     // ### only one signature supported
00475     if ( !signatures.empty() ) {
00476       kDebug() << "\nFound signature";
00477       GpgME::Signature signature = signatures.front();
00478 
00479       messagePart.status_code = signatureToStatus( signature );
00480       messagePart.status = QString::fromLocal8Bit( signature.status().asString() );
00481       for ( uint i = 1; i < signatures.size(); ++i ) {
00482         if ( signatureToStatus( signatures[i] ) != messagePart.status_code ) {
00483           messagePart.status_code = GPGME_SIG_STAT_DIFF;
00484           messagePart.status = i18n("Different results for signatures");
00485         }
00486       }
00487       if ( messagePart.status_code & GPGME_SIG_STAT_GOOD )
00488         messagePart.isGoodSignature = true;
00489 
00490       // get key for this signature
00491       Kleo::KeyListJob *job = cryptProto->keyListJob();
00492       std::vector<GpgME::Key> keys;
00493       if ( signature.fingerprint() ) // if the fingerprint is empty, the keylisting would return all available keys
00494         GpgME::KeyListResult keyListRes = job->exec( QStringList( QString::fromLatin1( signature.fingerprint() ) ),
00495                                                      false, keys );
00496       GpgME::Key key;
00497       if ( keys.size() == 1 )
00498         key = keys[0];
00499       else if ( keys.size() > 1 )
00500         assert( false ); // ### wtf, what should we do in this case??
00501 
00502       // save extended signature status flags
00503       messagePart.sigSummary = signature.summary();
00504 
00505       if ( key.keyID() )
00506         messagePart.keyId = key.keyID();
00507       if ( messagePart.keyId.isEmpty() )
00508         messagePart.keyId = signature.fingerprint();
00509       // ### Ugh. We depend on two enums being in sync:
00510       messagePart.keyTrust = (Kpgp::Validity)signature.validity();
00511       if ( key.numUserIDs() > 0 && key.userID( 0 ).id() )
00512         messagePart.signer = Kleo::DN( key.userID( 0 ).id() ).prettyDN();
00513       for ( uint iMail = 0; iMail < key.numUserIDs(); ++iMail ) {
00514         // The following if /should/ always result in TRUE but we
00515         // won't trust implicitely the plugin that gave us these data.
00516         if ( key.userID( iMail ).email() ) {
00517           QString email = QString::fromUtf8( key.userID( iMail ).email() );
00518           // ### work around gpgme 0.3.x / cryptplug bug where the
00519           // ### email addresses are specified as angle-addr, not addr-spec:
00520           if ( email.startsWith( '<' ) && email.endsWith( '>' ) )
00521             email = email.mid( 1, email.length() - 2 );
00522           if ( !email.isEmpty() )
00523             messagePart.signerMailAddresses.append( email );
00524         }
00525       }
00526 
00527       if ( signature.creationTime() )
00528         messagePart.creationTime.setTime_t( signature.creationTime() );
00529       else
00530         messagePart.creationTime = QDateTime();
00531       if ( messagePart.signer.isEmpty() ) {
00532         if ( key.numUserIDs() > 0 && key.userID( 0 ).name() )
00533           messagePart.signer = Kleo::DN( key.userID( 0 ).name() ).prettyDN();
00534         if ( !messagePart.signerMailAddresses.empty() ) {
00535           if ( messagePart.signer.isEmpty() )
00536             messagePart.signer = messagePart.signerMailAddresses.front();
00537           else
00538             messagePart.signer += " <" + messagePart.signerMailAddresses.front() + '>';
00539         }
00540       }
00541 
00542       kDebug() << "\n  key id:" << messagePart.keyId
00543                << "\n  key trust:" << messagePart.keyTrust
00544                << "\n  signer:" << messagePart.signer;
00545 
00546     } else {
00547       messagePart.creationTime = QDateTime();
00548     }
00549 
00550     if ( !doCheck || !data ){
00551       if ( cleartextData || !cleartext.isEmpty() ) {
00552         if ( mReader )
00553           htmlWriter()->queue( writeSigstatHeader( messagePart,
00554                                                    cryptProto,
00555                                                    fromAddress ) );
00556         bIsOpaqueSigned = true;
00557 
00558         CryptoProtocolSaver cpws( this, cryptProto );
00559         insertAndParseNewChildNode( sign, doCheck ? cleartext.data() : cleartextData->data(),
00560                                     "opaqued signed data" );
00561 
00562         if ( mReader )
00563           htmlWriter()->queue( writeSigstatFooter( messagePart ) );
00564 
00565       }
00566       else if ( !hideErrors ) {
00567         QString txt;
00568         txt = "<hr><b><h2>";
00569         txt.append( i18n( "The crypto engine returned no cleartext data." ) );
00570         txt.append( "</h2></b>" );
00571         txt.append( "<br>&nbsp;<br>" );
00572         txt.append( i18n( "Status: " ) );
00573         if ( !messagePart.status.isEmpty() ) {
00574           txt.append( "<i>" );
00575           txt.append( messagePart.status );
00576           txt.append( "</i>" );
00577         }
00578         else
00579           txt.append( i18n("(unknown)") );
00580         if ( mReader )
00581           htmlWriter()->queue(txt);
00582       }
00583     }
00584     else {
00585       if ( mReader ) {
00586         if ( !cryptProto ) {
00587           QString errorMsg;
00588           switch ( cryptPlugError ) {
00589           case NOT_INITIALIZED:
00590             errorMsg = i18n( "Crypto plug-in \"%1\" is not initialized.",
00591                          cryptPlugLibName );
00592             break;
00593           case CANT_VERIFY_SIGNATURES:
00594             errorMsg = i18n( "Crypto plug-in \"%1\" cannot verify signatures.",
00595                          cryptPlugLibName );
00596             break;
00597           case NO_PLUGIN:
00598             if ( cryptPlugDisplayName.isEmpty() )
00599               errorMsg = i18n( "No appropriate crypto plug-in was found." );
00600             else
00601               errorMsg = i18nc( "%1 is either 'OpenPGP' or 'S/MIME'",
00602                                "No %1 plug-in was found.",
00603                              cryptPlugDisplayName );
00604             break;
00605           }
00606           messagePart.errorText = i18n( "The message is signed, but the "
00607                                         "validity of the signature cannot be "
00608                                         "verified.<br />"
00609                                         "Reason: %1",
00610                                     errorMsg );
00611         }
00612 
00613         if ( mReader )
00614           htmlWriter()->queue( writeSigstatHeader( messagePart,
00615                                                    cryptProto,
00616                                                  fromAddress ) );
00617       }
00618 
00619       ObjectTreeParser otp( mReader, cryptProto, true );
00620       otp.parseObjectTree( data );
00621       mRawReplyString += otp.rawReplyString();
00622       mTextualContent += otp.textualContent();
00623       if ( !otp.textualContentCharset().isEmpty() )
00624         mTextualContentCharset = otp.textualContentCharset();
00625 
00626       if ( mReader )
00627         htmlWriter()->queue( writeSigstatFooter( messagePart ) );
00628     }
00629 
00630     kDebug() << "done, returning" << ( bIsOpaqueSigned ? "TRUE" : "FALSE" );
00631     return bIsOpaqueSigned;
00632   }
00633 
00634 
00635 bool ObjectTreeParser::okDecryptMIME( partNode& data,
00636                                       QByteArray& decryptedData,
00637                                       bool& signatureFound,
00638                                       std::vector<GpgME::Signature> &signatures,
00639                                       bool showWarning,
00640                                       bool& passphraseError,
00641                                       bool& actuallyEncrypted,
00642                                       QString& aErrorText,
00643                                       QString& auditLog )
00644 {
00645   passphraseError = false;
00646   aErrorText.clear();
00647   auditLog.clear();
00648   bool bDecryptionOk = false;
00649   enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT }
00650     cryptPlugError = NO_PLUGIN;
00651 
00652   const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
00653 
00654   QString cryptPlugLibName;
00655   if ( cryptProto )
00656     cryptPlugLibName = cryptProto->name();
00657 
00658   if ( mReader && !mReader->decryptMessage() ) {
00659     QString iconName = KIconLoader::global()->iconPath( "document-decrypt",
00660                                                         KIconLoader::Small );
00661     decryptedData = "<div style=\"font-size:large; text-align:center;"
00662                       "padding-top:20pt;\">"
00663                     + i18n("This message is encrypted.").toUtf8()
00664                     + "</div>"
00665                       "<div style=\"text-align:center; padding-bottom:20pt;\">"
00666                       "<a href=\"kmail:decryptMessage\">"
00667                       "<img src=\"" + iconName.toUtf8() + "\"/>"
00668                     + i18n("Decrypt Message").toUtf8()
00669                     + "</a></div>";
00670     return false;
00671   }
00672 
00673   if ( cryptProto && !kmkernel->contextMenuShown() ) {
00674     QByteArray ciphertext = data.msgPart().bodyDecodedBinary();
00675 #ifdef MARCS_DEBUG
00676     QString cipherStr = QString::fromLatin1( ciphertext );
00677     bool cipherIsBinary = ( !cipherStr.contains("BEGIN ENCRYPTED MESSAGE", Qt::CaseInsensitive ) ) &&
00678                           ( !cipherStr.contains("BEGIN PGP ENCRYPTED MESSAGE", Qt::CaseInsensitive ) ) &&
00679                           ( !cipherStr.contains("BEGIN PGP MESSAGE", Qt::CaseInsensitive ) );
00680 
00681     dumpToFile( "dat_04_reader.encrypted", ciphertext.data(), ciphertext.size() );
00682 
00683     QByteArray deb;
00684     deb =  "\n\nE N C R Y P T E D    D A T A = ";
00685     if ( cipherIsBinary )
00686       deb += "[binary data]";
00687     else {
00688       deb += "\"";
00689       deb += cipherStr;
00690       deb += "\"";
00691     }
00692     deb += "\n\n";
00693     kDebug() << deb;
00694 #endif
00695 
00696 
00697     kDebug() << "going to call CRYPTPLUG" << cryptPlugLibName;
00698     if ( mReader )
00699       emit mReader->noDrag(); // in case pineentry pops up, don't let kmheaders start a drag afterwards
00700 
00701     Kleo::DecryptVerifyJob* job = cryptProto->decryptVerifyJob();
00702     if ( !job ) {
00703       cryptPlugError = CANT_DECRYPT;
00704       cryptProto = 0;
00705     } else {
00706       QByteArray plainText;
00707       KleoJobExecutor executor;
00708       const std::pair<GpgME::DecryptionResult,GpgME::VerificationResult> res = executor.exec( job, ciphertext, plainText );
00709       const GpgME::DecryptionResult decryptResult = res.first;
00710       const GpgME::VerificationResult verifyResult = res.second;
00711       signatureFound = verifyResult.signatures().size() > 0;
00712       signatures = verifyResult.signatures();
00713       bDecryptionOk = !decryptResult.error();
00714       passphraseError =  decryptResult.error().isCanceled()
00715         || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
00716       actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA;
00717       aErrorText = QString::fromLocal8Bit( decryptResult.error().asString() );
00718       auditLog = executor.auditLogAsHtml();
00719 
00720       kDebug() << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG";
00721       if ( bDecryptionOk )
00722         decryptedData = plainText;
00723       else if ( mReader && showWarning ) {
00724         decryptedData = "<div style=\"font-size:x-large; text-align:center;"
00725                         "padding:20pt;\">"
00726                       + i18n("Encrypted data not shown.").toUtf8()
00727                       + "</div>";
00728         if ( !passphraseError )
00729           aErrorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.", cryptPlugLibName )
00730                     + "<br />"
00731                     + i18n("Error: %1", aErrorText );
00732       }
00733     }
00734   }
00735 
00736   if ( !cryptProto ) {
00737     decryptedData = "<div style=\"text-align:center; padding:20pt;\">"
00738                   + i18n("Encrypted data not shown.").toUtf8()
00739                   + "</div>";
00740     switch ( cryptPlugError ) {
00741     case NOT_INITIALIZED:
00742       aErrorText = i18n( "Crypto plug-in \"%1\" is not initialized.",
00743                        cryptPlugLibName );
00744       break;
00745     case CANT_DECRYPT:
00746       aErrorText = i18n( "Crypto plug-in \"%1\" cannot decrypt messages.",
00747                        cryptPlugLibName );
00748       break;
00749     case NO_PLUGIN:
00750       aErrorText = i18n( "No appropriate crypto plug-in was found." );
00751       break;
00752     }
00753   } else if ( kmkernel->contextMenuShown() ) {
00754     // ### Workaround for bug 56693 (kmail freeze with the complete desktop
00755     // ### while pinentry-qt appears)
00756     QByteArray ciphertext( data.msgPart().bodyDecodedBinary() );
00757     QString cipherStr = QString::fromLatin1( ciphertext );
00758     bool cipherIsBinary = ( !cipherStr.contains("BEGIN ENCRYPTED MESSAGE", Qt::CaseInsensitive ) ) &&
00759                           ( !cipherStr.contains("BEGIN PGP ENCRYPTED MESSAGE", Qt::CaseInsensitive ) ) &&
00760                           ( !cipherStr.contains("BEGIN PGP MESSAGE", Qt::CaseInsensitive ) );
00761     if ( !cipherIsBinary ) {
00762       decryptedData = ciphertext;
00763     }
00764     else {
00765       decryptedData = "<div style=\"font-size:x-large; text-align:center;"
00766                       "padding:20pt;\">"
00767                     + i18n("Encrypted data not shown.").toUtf8()
00768                     + "</div>";
00769     }
00770   }
00771 
00772   dumpToFile( "dat_05_reader.decrypted", decryptedData.data(), decryptedData.size() );
00773 
00774   return bDecryptionOk;
00775 }
00776 
00777   //static
00778   bool ObjectTreeParser::containsExternalReferences( const QString & str )
00779   {
00780     int httpPos = str.indexOf( "\"http:", Qt::CaseInsensitive );
00781     int httpsPos = str.indexOf( "\"https:", Qt::CaseInsensitive );
00782 
00783     while ( httpPos >= 0 || httpsPos >= 0 ) {
00784       // pos = index of next occurrence of "http: or "https: whichever comes first
00785       int pos = ( httpPos < httpsPos )
00786                 ? ( ( httpPos >= 0 ) ? httpPos : httpsPos )
00787                 : ( ( httpsPos >= 0 ) ? httpsPos : httpPos );
00788       // look backwards for "href"
00789       if ( pos > 5 ) {
00790         int hrefPos = str.lastIndexOf( "href", pos - 5, Qt::CaseInsensitive );
00791         // if no 'href' is found or the distance between 'href' and '"http[s]:'
00792         // is larger than 7 (7 is the distance in 'href = "http[s]:') then
00793         // we assume that we have found an external reference
00794         if ( ( hrefPos == -1 ) || ( pos - hrefPos > 7 ) ) {
00795 
00796           // HTML messages created by KMail itself for now contain the following:
00797           // <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
00798           // Make sure not to show an external references warning for this string
00799           int dtdPos = str.indexOf( "http://www.w3.org/TR/REC-html40/strict.dtd", pos + 1 );
00800           if ( dtdPos != ( pos + 1 ) )
00801             return true;
00802         }
00803       }
00804       // find next occurrence of "http: or "https:
00805       if ( pos == httpPos ) {
00806         httpPos = str.indexOf( "\"http:", httpPos + 6, Qt::CaseInsensitive );
00807       }
00808       else {
00809         httpsPos = str.indexOf( "\"https:", httpsPos + 7, Qt::CaseInsensitive );
00810       }
00811     }
00812     return false;
00813   }
00814 
00815   bool ObjectTreeParser::processTextHtmlSubtype( partNode * curNode, ProcessResult & ) {
00816     const QByteArray partBody( curNode->msgPart().bodyDecoded() );
00817 
00818     mRawReplyString = partBody;
00819     if ( curNode->isFirstTextPart() ) {
00820       mTextualContent += curNode->msgPart().bodyToUnicode();
00821       mTextualContentCharset = curNode->msgPart().charset();
00822     }
00823 
00824     if ( !mReader )
00825       return true;
00826 
00827     QString bodyText;
00828     if ( mReader->htmlMail() )
00829       bodyText = codecFor( curNode )->toUnicode( partBody );
00830     else
00831       bodyText = KMMessage::html2source( partBody );
00832 
00833     if ( curNode->isFirstTextPart() ||
00834          attachmentStrategy()->defaultDisplay( curNode ) == AttachmentStrategy::Inline ||
00835          showOnlyOneMimePart() )
00836     {
00837       if ( mReader->htmlMail() ) {
00838 
00839         // Strip <html>, <head>, and <body>, so we don't end up having those tags
00840         // twice, which confuses KHTML (especially with a signed
00841         // multipart/alternative message, the signature bars get rendered at the
00842         // wrong place)
00843         bodyText = bodyText.replace( "<html>", QString(), Qt::CaseInsensitive );
00844         bodyText = bodyText.replace( "<head>", QString(), Qt::CaseInsensitive );
00845         bodyText = bodyText.replace( "</head>", QString(), Qt::CaseInsensitive );
00846         QRegExp bodyRegExp( "<body.*>" ); //the body tag might have additional attributes,
00847         bodyRegExp.setMinimal( true );    //so make sure to match them as well
00848         bodyRegExp.setCaseSensitivity( Qt::CaseInsensitive );
00849         bodyText = bodyText.replace( bodyRegExp, QString() );
00850 
00851         // Strip </BODY> and </HTML> from end.
00852         // We must do this, or else the message will not be displayed correctly
00853         // because we have two </body> or </html> tags in the generated HTML.
00854         // It is IMHO enough to search only for </BODY> and put \0 there.
00855         int i = bodyText.lastIndexOf( "</body>", -1, Qt::CaseInsensitive );
00856         if ( 0 <= i )
00857           bodyText.truncate(i);
00858         else { // just in case - search for </html>
00859           i = bodyText.lastIndexOf( "</html>", -1, Qt::CaseInsensitive );
00860           if ( 0 <= i )
00861             bodyText.truncate(i);
00862         }
00863 
00864         // Show the "external references" warning (with possibility to load
00865         // external references only if loading external references is disabled
00866         // and the HTML code contains obvious external references). For
00867         // messages where the external references are obfuscated the user won't
00868         // have an easy way to load them but that shouldn't be a problem
00869         // because only spam contains obfuscated external references.
00870         if ( !mReader->htmlLoadExternal() &&
00871              containsExternalReferences( bodyText ) ) {
00872           htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
00873           htmlWriter()->queue( i18n("<b>Note:</b> This HTML message may contain external "
00874                                     "references to images etc. For security/privacy reasons "
00875                                     "external references are not loaded. If you trust the "
00876                                     "sender of this message then you can load the external "
00877                                     "references for this message "
00878                                     "<a href=\"kmail:loadExternal\">by clicking here</a>.") );
00879           htmlWriter()->queue( "</div><br><br>" );
00880         }
00881       } else {
00882         htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
00883         htmlWriter()->queue( i18n("<b>Note:</b> This is an HTML message. For "
00884                                   "security reasons, only the raw HTML code "
00885                                   "is shown. If you trust the sender of this "
00886                                   "message then you can activate formatted "
00887                                   "HTML display for this message "
00888                                   "<a href=\"kmail:showHTML\">by clicking here</a>.") );
00889         htmlWriter()->queue( "</div><br><br>" );
00890       }
00891       htmlWriter()->queue( bodyText );
00892       mReader->mColorBar->setHtmlMode();
00893       return true;
00894     }
00895     return false;
00896   }
00897 } // namespace KMail
00898 
00899 static bool isMailmanMessage( partNode * curNode ) {
00900   if ( !curNode->dwPart() || !curNode->dwPart()->hasHeaders() )
00901     return false;
00902   DwHeaders & headers = curNode->dwPart()->Headers();
00903   if ( headers.HasField("X-Mailman-Version") )
00904     return true;
00905   if ( headers.HasField("X-Mailer") &&
00906        0 == QString::fromLatin1( headers.FieldBody("X-Mailer").AsString().c_str() )
00907        .contains("MAILMAN", Qt::CaseInsensitive ) )
00908     return true;
00909   return false;
00910 }
00911 
00912 namespace KMail {
00913 
00914   bool ObjectTreeParser::processMailmanMessage( partNode * curNode ) {
00915     const QString str = QString::fromLatin1( curNode->msgPart().bodyDecoded() );
00916 
00917     //###
00918     const QLatin1String delim1( "--__--__--\n\nMessage:");
00919     const QLatin1String delim2( "--__--__--\r\n\r\nMessage:");
00920     const QLatin1String delimZ2("--__--__--\n\n_____________");
00921     const QLatin1String delimZ1("--__--__--\r\n\r\n_____________");
00922     QString partStr, digestHeaderStr;
00923     int thisDelim = str.indexOf(delim1, Qt::CaseInsensitive );
00924     if ( thisDelim == -1 )
00925       thisDelim = str.indexOf(delim2, Qt::CaseInsensitive );
00926     if ( thisDelim == -1 ) {
00927       kDebug() << "        Sorry: Old style Mailman message but no delimiter found.";
00928       return false;
00929     }
00930 
00931     int nextDelim = str.indexOf( delim1, thisDelim+1, Qt::CaseInsensitive );
00932     if ( -1 == nextDelim )
00933       nextDelim = str.indexOf( delim2, thisDelim+1, Qt::CaseInsensitive );
00934     if ( -1 == nextDelim )
00935       nextDelim = str.indexOf( delimZ1, thisDelim+1, Qt::CaseInsensitive );
00936     if ( -1 == nextDelim )
00937       nextDelim = str.indexOf( delimZ2, thisDelim+1, Qt::CaseInsensitive );
00938     if ( nextDelim < 0)
00939       return false;
00940 
00941     kDebug() << "        processing old style Mailman digest";
00942     //if ( curNode->mRoot )
00943     //  curNode = curNode->mRoot;
00944 
00945     // at least one message found: build a mime tree
00946     digestHeaderStr = "Content-Type=text/plain\nContent-Description=digest header\n\n";
00947     digestHeaderStr += str.mid( 0, thisDelim );
00948     insertAndParseNewChildNode( *curNode,
00949                                 digestHeaderStr.toLatin1(),
00950                                 "Digest Header", true );
00951     //mReader->queueHtml("<br><hr><br>");
00952     // temporarily change curent node's Content-Type
00953     // to get our embedded RfC822 messages properly inserted
00954     curNode->setType(    DwMime::kTypeMultipart );
00955     curNode->setSubType( DwMime::kSubtypeDigest );
00956     while( -1 < nextDelim ){
00957       int thisEoL = str.indexOf("\nMessage:", thisDelim, Qt::CaseInsensitive );
00958       if ( -1 < thisEoL )
00959         thisDelim = thisEoL+1;
00960       else{
00961         thisEoL = str.indexOf("\n_____________", thisDelim, Qt::CaseInsensitive );
00962         if ( -1 < thisEoL )
00963           thisDelim = thisEoL+1;
00964       }
00965       thisEoL = str.indexOf( '\n', thisDelim );
00966       if ( -1 < thisEoL )
00967         thisDelim = thisEoL+1;
00968       else
00969         thisDelim = thisDelim+1;
00970       //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] )
00971       //  ++thisDelim;
00972 
00973       partStr = "Content-Type=message/rfc822\nContent-Description=embedded message\n";
00974       partStr += str.mid( thisDelim, nextDelim-thisDelim );
00975       QString subject = QString::fromLatin1("embedded message");
00976       QString subSearch = QString::fromLatin1("\nSubject:");
00977       int subPos = partStr.indexOf(subSearch, 0, Qt::CaseInsensitive );
00978       if ( -1 < subPos ){
00979         subject = partStr.mid(subPos+subSearch.length());
00980         thisEoL = subject.indexOf('\n');
00981         if ( -1 < thisEoL )
00982           subject.truncate( thisEoL );
00983       }
00984       kDebug() << "        embedded message found: \"" << subject <<"\"";
00985       insertAndParseNewChildNode( *curNode,
00986                                   partStr.toLatin1(),
00987                                   subject.toLatin1(), true );
00988       //mReader->queueHtml("<br><hr><br>");
00989       thisDelim = nextDelim+1;
00990       nextDelim = str.indexOf(delim1, thisDelim, Qt::CaseInsensitive );
00991       if ( -1 == nextDelim )
00992         nextDelim = str.indexOf(delim2, thisDelim, Qt::CaseInsensitive);
00993       if ( -1 == nextDelim )
00994         nextDelim = str.indexOf(delimZ1, thisDelim, Qt::CaseInsensitive);
00995       if ( -1 == nextDelim )
00996         nextDelim = str.indexOf(delimZ2, thisDelim, Qt::CaseInsensitive);
00997     }
00998     // reset curent node's Content-Type
00999     curNode->setType(    DwMime::kTypeText );
01000     curNode->setSubType( DwMime::kSubtypePlain );
01001     int thisEoL = str.indexOf( "_____________", thisDelim );
01002     if ( -1 < thisEoL ){
01003       thisDelim = thisEoL;
01004       thisEoL = str.indexOf( '\n', thisDelim );
01005       if ( -1 < thisEoL )
01006         thisDelim = thisEoL+1;
01007     }
01008     else
01009       thisDelim = thisDelim+1;
01010     partStr = "Content-Type=text/plain\nContent-Description=digest footer\n\n";
01011     partStr += str.mid( thisDelim );
01012     insertAndParseNewChildNode( *curNode,
01013                                 partStr.toLatin1(),
01014                                 "Digest Footer", true );
01015     return true;
01016   }
01017 
01018   bool ObjectTreeParser::processTextPlainSubtype( partNode * curNode, ProcessResult & result ) {
01019     if ( !mReader ) {
01020       mRawReplyString = curNode->msgPart().bodyDecoded();
01021       if ( curNode->isFirstTextPart() ) {
01022         mTextualContent += curNode->msgPart().bodyToUnicode();
01023         mTextualContentCharset = curNode->msgPart().charset();
01024       }
01025       return true;
01026     }
01027 
01028     if ( !curNode->isFirstTextPart() &&
01029          attachmentStrategy()->defaultDisplay( curNode ) !=