diff --git a/kmail/objecttreeparser.cpp b/kmail/objecttreeparser.cpp index 0f6664d604..6713adf679 100644 --- a/kmail/objecttreeparser.cpp +++ b/kmail/objecttreeparser.cpp @@ -1,3298 +1,3303 @@ /* -*- mode: C++; c-file-style: "gnu" -*- objecttreeparser.cpp This file is part of KMail, the KDE mail client. Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB Copyright (c) 2003 Marc Mutz KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include // my header file #include "objecttreeparser.h" #include "objecttreeparser_p.h" // other KMail headers #include "kmkernel.h" #include "kmreaderwin.h" #include "partNode.h" #include #include #include "partmetadata.h" #include "attachmentstrategy.h" #include "interfaces/htmlwriter.h" #include "htmlstatusbar.h" #include "csshelper.h" #include "bodypartformatter.h" #include "bodypartformatterfactory.h" #include "partnodebodypart.h" #include "interfaces/bodypartformatter.h" #include "globalsettings.h" #include "util.h" #include "callback.h" // other module headers #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // other KDE headers #include #include #include #include #include #include #include #include #include #include #include // other Qt headers #include #include #include #include #include #include #include #include #include // other headers #include #include #include #include #include #include #include "chiasmuskeyselector.h" #include /* RFC822 messages can be embedded as often as one likes and at some * point display of multiple embedded levels leads to a denial of service * as the message parsing is not really that efficient and parses * the whole message body again and again. The display of those messages * is also not very performant as there are several rendering steps for * each level. So we enforce an arbitrary limit here after which we just * show a warning. */ #define MAX_RFC822_EMBEDD_DEPTH 10 /* Should be enough for most cases. */ /* For old PGP2 keys that used MD5 as the algorithm for UserID ceritifations * some versions ( at least 2.0.26 - 2.0.28 ) of GnuPG return a KeyID of zero. * This can lead kmail to show invalid signature information. As gnupg * returns any PGP2 key when keyinformation about those keys is requested. * This is a workaround for those broken GnuPG versions. * GnuPG 2.1 will ignore those keys. * See https://bugs.g10code.com/gnupg/issue2000 */ #define GNUPG_BAD_DIGEST_KEYID "0000000000000000" namespace KMail { // A small class that eases temporary CryptPlugWrapper changes: class ObjectTreeParser::CryptoProtocolSaver { ObjectTreeParser * otp; const Kleo::CryptoBackend::Protocol * protocol; public: CryptoProtocolSaver( ObjectTreeParser * _otp, const Kleo::CryptoBackend::Protocol* _w ) : otp( _otp ), protocol( _otp ? _otp->cryptoProtocol() : 0 ) { if ( otp ) otp->setCryptoProtocol( _w ); } ~CryptoProtocolSaver() { if ( otp ) otp->setCryptoProtocol( protocol ); } }; ObjectTreeParser::ObjectTreeParser( KMReaderWin * reader, const Kleo::CryptoBackend::Protocol * protocol, bool showOnlyOneMimePart, bool keepEncryptions, bool includeSignatures, const AttachmentStrategy * strategy, HtmlWriter * htmlWriter, CSSHelper * cssHelper ) : mReader( reader ), mCryptoProtocol( protocol ), mShowOnlyOneMimePart( showOnlyOneMimePart ), mKeepEncryptions( keepEncryptions ), mIncludeSignatures( includeSignatures ), mHasPendingAsyncJobs( false ), mAllowAsync( false ), mShowRawToltecMail( false ), mIsHTMLAlternativeAvailable( false ), mRFC822RecursionCnt( 0 ), mAttachmentStrategy( strategy ), mHtmlWriter( htmlWriter ), mCSSHelper( cssHelper ) { if ( !attachmentStrategy() ) mAttachmentStrategy = reader ? reader->attachmentStrategy() : AttachmentStrategy::smart(); if ( reader && !this->htmlWriter() ) mHtmlWriter = reader->htmlWriter(); if ( reader && !this->cssHelper() ) mCSSHelper = reader->mCSSHelper; } ObjectTreeParser::ObjectTreeParser( const ObjectTreeParser & other ) : mReader( other.mReader ), mCryptoProtocol( other.cryptoProtocol() ), mShowOnlyOneMimePart( other.showOnlyOneMimePart() ), mKeepEncryptions( other.keepEncryptions() ), mIncludeSignatures( other.includeSignatures() ), mHasPendingAsyncJobs( other.hasPendingAsyncJobs() ), mAllowAsync( other.allowAsync() ), mIsHTMLAlternativeAvailable( other.isHTMLAlternativeAvailable() ), mRFC822RecursionCnt( other.rfc822RecursionCnt() ), mAttachmentStrategy( other.attachmentStrategy() ), mHtmlWriter( other.htmlWriter() ), mCSSHelper( other.cssHelper() ) { } ObjectTreeParser::~ObjectTreeParser() {} void ObjectTreeParser::insertAndParseNewChildNode( partNode& startNode, const char* content, const char* cntDesc, bool append, bool addToTextualContent ) { DwBodyPart* myBody = new DwBodyPart( DwString( content ), 0 ); myBody->Parse(); if ( ( !myBody->Body().FirstBodyPart() || myBody->Body().AsString().length() == 0 ) && startNode.dwPart() && startNode.dwPart()->Body().Message() && startNode.dwPart()->Body().Message()->Body().FirstBodyPart() ) { // if encapsulated imap messages are loaded the content-string is not complete // so we need to keep the child dwparts myBody = new DwBodyPart( *(startNode.dwPart()->Body().Message()) ); } if ( myBody->hasHeaders() ) { DwText& desc = myBody->Headers().ContentDescription(); desc.FromString( cntDesc ); desc.SetModified(); myBody->Headers().Parse(); } partNode* parentNode = &startNode; partNode* newNode = new partNode(false, myBody); // Build the object tree of the new node before setting the parent, as otherwise // buildObjectTree() would erronously modify the parents as well newNode->buildObjectTree( false ); if ( append && parentNode->firstChild() ) { parentNode = parentNode->firstChild(); while( parentNode->nextSibling() ) parentNode = parentNode->nextSibling(); parentNode->setNext( newNode ); } else parentNode->setFirstChild( newNode ); if ( startNode.mimePartTreeItem() ) { newNode->fillMimePartTree( startNode.mimePartTreeItem(), 0, QString::null, QString::null, QString::null, 0, append ); } else { } ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.setRFC822RecursionCnt( mRFC822RecursionCnt + 1 ); otp.parseObjectTree( newNode ); if ( addToTextualContent ) { mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); } } //----------------------------------------------------------------------------- void ObjectTreeParser::parseObjectTree( partNode * node ) { //kdDebug(5006) << "ObjectTreeParser::parseObjectTree( " // << (node ? "node OK, " : "no node, ") // << "showOnlyOneMimePart: " << (showOnlyOneMimePart() ? "TRUE" : "FALSE") // << " )" << endl; if ( !node ) return; // reset pending async jobs state (we'll rediscover pending jobs as we go) mHasPendingAsyncJobs = false; // reset "processed" flags for... if ( showOnlyOneMimePart() ) { // ... this node and all descendants node->setProcessed( false, false ); if ( partNode * child = node->firstChild() ) child->setProcessed( false, true ); } else if ( mReader && !node->parentNode() ) { // ...this node and all it's siblings and descendants node->setProcessed( false, true ); } // Make sure the whole content is relative, so that nothing is painted over the header // if a malicious message uses absolute positioning. // Also force word wrapping, which is useful for printing, see https://issues.kolab.org/issue3992. const bool isRoot = node == node->topLevelParent(); if ( isRoot && htmlWriter() ) { htmlWriter()->queue( "
\n" ); } for ( ; node ; node = node->nextSibling() ) { if ( node->processed() ) continue; ProcessResult processResult; if ( mReader ) { htmlWriter()->queue( QString::fromLatin1("").arg( node->nodeId() ) ); } if ( const Interface::BodyPartFormatter * formatter = BodyPartFormatterFactory::instance()->createFor( node->typeString(), node->subTypeString() ) ) { // Only use the external plugin if we have a reader. Otherwise, just do nothing for this // node. if ( mReader ) { PartNodeBodyPart part( *node, codecFor( node ) ); // Set the default display strategy for this body part relying on the // identity of KMail::Interface::BodyPart::Display and AttachmentStrategy::Display part.setDefaultDisplay( (KMail::Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay( node ) ); writeAttachmentMarkHeader( node ); Callback callback( mReader->message(), mReader ); const Interface::BodyPartFormatter::Result result = formatter->format( &part, htmlWriter(), callback ); switch ( result ) { case Interface::BodyPartFormatter::AsIcon: processResult.setNeverDisplayInline( true ); // fall through: case Interface::BodyPartFormatter::Failed: defaultHandling( node, processResult ); break; case Interface::BodyPartFormatter::Ok: case Interface::BodyPartFormatter::NeedContent: node->setDisplayedEmbedded( true ); // FIXME: incomplete content handling ; } writeAttachmentMarkFooter(); } } else { const BodyPartFormatter * bpf = BodyPartFormatter::createFor( node->type(), node->subType() ); kdFatal( !bpf, 5006 ) << "THIS SHOULD NO LONGER HAPPEN (" << node->typeString() << '/' << node->subTypeString() << ')' << endl; writeAttachmentMarkHeader( node ); if ( bpf && !bpf->process( this, node, processResult ) ) { defaultHandling( node, processResult ); } writeAttachmentMarkFooter(); } node->setProcessed( true, false ); // adjust signed/encrypted flags if inline PGP was found processResult.adjustCryptoStatesOfNode( node ); if ( showOnlyOneMimePart() ) break; } if ( isRoot && htmlWriter() ) { htmlWriter()->queue( "
\n" ); } } void ObjectTreeParser::defaultHandling( partNode * node, ProcessResult & result ) { // ### (mmutz) default handling should go into the respective // ### bodypartformatters. if ( !mReader ) return; const AttachmentStrategy * as = attachmentStrategy(); if ( as && as->defaultDisplay( node ) == AttachmentStrategy::None && !showOnlyOneMimePart() && node->parentNode() /* message is not an attachment */ ) { node->setDisplayedHidden( true ); return; } bool asIcon = true; if ( showOnlyOneMimePart() ) // ### (mmutz) this is wrong! If I click on an image part, I // want the equivalent of "view...", except for the extra // window! asIcon = !node->hasContentDispositionInline(); else if ( !result.neverDisplayInline() ) if ( as ) asIcon = as->defaultDisplay( node ) == AttachmentStrategy::AsIcon; // neither image nor text -> show as icon if ( !result.isImage() && node->type() != DwMime::kTypeText ) asIcon = true; // if the image is not complete do not try to show it inline if ( result.isImage() && !node->msgPart().isComplete() ) asIcon = true; if ( asIcon ) { if ( !( as && as->defaultDisplay( node ) == AttachmentStrategy::None ) || showOnlyOneMimePart() ) { writePartIcon( &node->msgPart(), node->nodeId() ); } else { node->setDisplayedHidden( true ); } } else if ( result.isImage() ) { node->setDisplayedEmbedded( true ); writePartIcon( &node->msgPart(), node->nodeId(), true ); } else { node->setDisplayedEmbedded( true ); writeBodyString( node->msgPart().bodyDecoded(), node->trueFromAddress(), codecFor( node ), result, false ); } // end of ### } void ProcessResult::adjustCryptoStatesOfNode( partNode * node ) const { if ( ( inlineSignatureState() != KMMsgNotSigned ) || ( inlineEncryptionState() != KMMsgNotEncrypted ) ) { node->setSignatureState( inlineSignatureState() ); node->setEncryptionState( inlineEncryptionState() ); } } ////////////////// ////////////////// ////////////////// static int signatureToStatus( const GpgME::Signature &sig ) { switch ( sig.status().code() ) { case GPG_ERR_NO_ERROR: return GPGME_SIG_STAT_GOOD; case GPG_ERR_BAD_SIGNATURE: return GPGME_SIG_STAT_BAD; case GPG_ERR_NO_PUBKEY: return GPGME_SIG_STAT_NOKEY; case GPG_ERR_NO_DATA: return GPGME_SIG_STAT_NOSIG; case GPG_ERR_SIG_EXPIRED: return GPGME_SIG_STAT_GOOD_EXP; case GPG_ERR_KEY_EXPIRED: return GPGME_SIG_STAT_GOOD_EXPKEY; default: return GPGME_SIG_STAT_ERROR; } } bool ObjectTreeParser::writeOpaqueOrMultipartSignedData( partNode* data, partNode& sign, const QString& fromAddress, bool doCheck, QCString* cleartextData, const std::vector & paramSignatures, bool hideErrors, - bool isPGPInline ) + bool isPGPInline, + const QTextCodec *aCodec) { bool bIsOpaqueSigned = false; enum { NO_PLUGIN, NOT_INITIALIZED, CANT_VERIFY_SIGNATURES } cryptPlugError = NO_PLUGIN; const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol(); QString cryptPlugLibName; QString cryptPlugDisplayName; if ( cryptProto ) { cryptPlugLibName = cryptProto->name(); cryptPlugDisplayName = cryptProto->displayName(); } #ifndef NDEBUG if ( !doCheck ) { //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: showing OpenPGP (Encrypted+Signed) data" << endl; } else { if ( data ) { //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Multipart Signed data" << endl; } else { //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Opaque Signed data" << endl; } } #endif if ( doCheck && cryptProto ) { //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: going to call CRYPTPLUG " // << cryptPlugLibName << endl; } QCString cleartext; QByteArray signaturetext; if ( doCheck && cryptProto ) { if ( data ) { cleartext = KMail::Util::CString( data->dwPart()->AsString() ); dumpToFile( "dat_01_reader_signedtext_before_canonicalization", cleartext.data(), cleartext.length() ); // replace simple LFs by CRLSs // according to RfC 2633, 3.1.1 Canonicalization //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl; cleartext = Util::lf2crlf( cleartext ); //kdDebug(5006) << " done." << endl; } dumpToFile( "dat_02_reader_signedtext_after_canonicalization", cleartext.data(), cleartext.length() ); signaturetext = sign.msgPart().bodyDecodedBinary(); dumpToFile( "dat_03_reader.sig", signaturetext.data(), signaturetext.size() ); } std::vector signatures; if ( !doCheck ) signatures = paramSignatures; PartMetaData messagePart; messagePart.isSigned = true; messagePart.technicalProblem = ( cryptProto == 0 ); messagePart.isGoodSignature = false; messagePart.isEncrypted = false; messagePart.isDecryptable = false; messagePart.keyTrust = Kpgp::KPGP_VALIDITY_UNKNOWN; messagePart.status = i18n("Wrong Crypto Plug-In."); messagePart.status_code = GPGME_SIG_STAT_NONE; GpgME::Key key; if ( doCheck && cryptProto ) { GpgME::VerificationResult result; if ( data ) { // detached const VerifyDetachedBodyPartMemento * m = dynamic_cast( sign.bodyPartMemento( "verifydetached" ) ); if ( !m ) { Kleo::VerifyDetachedJob * job = cryptProto->verifyDetachedJob(); if ( !job ) { cryptPlugError = CANT_VERIFY_SIGNATURES; // PENDING(marc) cryptProto = 0 here? } else { QByteArray plainData = cleartext; plainData.resize( cleartext.size() - 1 ); VerifyDetachedBodyPartMemento * newM = new VerifyDetachedBodyPartMemento( job, cryptProto->keyListJob(), signaturetext, plainData ); if ( allowAsync() ) { if ( newM->start() ) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } sign.setBodyPartMemento( "verifydetached", newM ); } } else if ( m->isRunning() ) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; m = 0; } if ( m ) { result = m->verifyResult(); messagePart.auditLogError = m->auditLogError(); messagePart.auditLog = m->auditLogAsHtml(); key = m->signingKey(); } } else { // opaque const VerifyOpaqueBodyPartMemento * m = dynamic_cast( sign.bodyPartMemento( "verifyopaque" ) ); if ( !m ) { Kleo::VerifyOpaqueJob * job = cryptProto->verifyOpaqueJob(); if ( !job ) { cryptPlugError = CANT_VERIFY_SIGNATURES; // PENDING(marc) cryptProto = 0 here? } else { VerifyOpaqueBodyPartMemento * newM = new VerifyOpaqueBodyPartMemento( job, cryptProto->keyListJob(), signaturetext ); if ( allowAsync() ) { if ( newM->start() ) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } sign.setBodyPartMemento( "verifyopaque", newM ); } } else if ( m->isRunning() ) { messagePart.inProgress = true; mHasPendingAsyncJobs = true; m = 0; } if ( m ) { result = m->verifyResult(); const QByteArray & plainData = m->plainText(); cleartext = QCString( plainData.data(), plainData.size() + 1 ); messagePart.auditLogError = m->auditLogError(); messagePart.auditLog = m->auditLogAsHtml(); key = m->signingKey(); } } std::stringstream ss; ss << result; //kdDebug(5006) << ss.str().c_str() << endl; signatures = result.signatures(); } if ( doCheck ) { //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: returned from CRYPTPLUG" << endl; } // ### only one signature supported if ( signatures.size() > 0 ) { //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: found signature" << endl; GpgME::Signature signature = signatures[0]; messagePart.status_code = signatureToStatus( signature ); messagePart.status = QString::fromUtf8( signature.status().asString() ); for ( uint i = 1; i < signatures.size(); ++i ) { if ( signatureToStatus( signatures[i] ) != messagePart.status_code ) { messagePart.status_code = GPGME_SIG_STAT_DIFF; messagePart.status = i18n("Different results for signatures"); } } if ( messagePart.status_code & GPGME_SIG_STAT_GOOD ) { messagePart.isGoodSignature = true; if ( !doCheck ) { // We have a good signature but did not do a verify, // this means the signature was already validated before by // decryptverify for example. Q_ASSERT( !key.keyID() ); // There should be no key set without doCheck // Search for the key by it's fingerprint so that we can check for // trust etc. Kleo::KeyListJob * job = cryptProto->keyListJob( false ); // local, no sigs if ( !job ) { kdDebug(5006) << "The Crypto backend does not support listing keys. " << endl; } else { std::vector found_keys; // As we are local it is ok to make this synchronous GpgME::KeyListResult res = job->exec( QStringList( signature.fingerprint() ), false, found_keys ); if ( res.error() ) { kdDebug(5006) << "Error while searching key for Fingerprint: " << signature.fingerprint() << endl; } if ( found_keys.size() > 1 ) { // Should not Happen kdDebug(5006) << "Oops: Found more then one Key for Fingerprint: " << signature.fingerprint() << endl; } if ( found_keys.size() != 1 ) { // Should not Happen at this point kdDebug(5006) << "Oops: Found no Key for Fingerprint: " << signature.fingerprint() << endl; } else { key = found_keys[0]; } } } } // save extended signature status flags messagePart.sigSummary = signature.summary(); if ( key.keyID() ) messagePart.keyId = key.keyID(); if ( messagePart.keyId.isEmpty() ) messagePart.keyId = signature.fingerprint(); // ### Ugh. We depend on two enums being in sync: messagePart.keyTrust = (Kpgp::Validity)signature.validity(); if ( key.numUserIDs() > 0 && key.userID( 0 ).id() ) messagePart.signer = Kleo::DN( key.userID( 0 ).id() ).prettyDN(); for ( uint iMail = 0; iMail < key.numUserIDs(); ++iMail ) { // The following if /should/ always result in TRUE but we // won't trust implicitely the plugin that gave us these data. if ( key.userID( iMail ).email() ) { QString email = QString::fromUtf8( key.userID( iMail ).email() ); // ### work around gpgme 0.3.x / cryptplug bug where the // ### email addresses are specified as angle-addr, not addr-spec: if ( email.startsWith( "<" ) && email.endsWith( ">" ) ) email = email.mid( 1, email.length() - 2 ); if ( !email.isEmpty() ) messagePart.signerMailAddresses.append( email ); } } if ( signature.creationTime() ) messagePart.creationTime.setTime_t( signature.creationTime() ); else messagePart.creationTime = QDateTime(); if ( messagePart.signer.isEmpty() ) { if ( key.numUserIDs() > 0 && key.userID( 0 ).name() ) messagePart.signer = Kleo::DN( key.userID( 0 ).name() ).prettyDN(); if ( !messagePart.signerMailAddresses.empty() ) { if ( messagePart.signer.isEmpty() ) messagePart.signer = messagePart.signerMailAddresses.front(); else messagePart.signer += " <" + messagePart.signerMailAddresses.front() + '>'; } } //kdDebug(5006) << "\n key id: " << messagePart.keyId // << "\n key trust: " << messagePart.keyTrust // << "\n signer: " << messagePart.signer << endl; } else { messagePart.creationTime = QDateTime(); } if ( !doCheck || !data ){ if ( cleartextData || !cleartext.isEmpty() ) { if ( mReader ) htmlWriter()->queue( writeSigstatHeader( messagePart, cryptProto, fromAddress ) ); bIsOpaqueSigned = true; if ( !isPGPInline ) { CryptoProtocolSaver cpws( this, cryptProto ); insertAndParseNewChildNode( sign, doCheck ? cleartext.data() : cleartextData->data(), "opaqued signed data" ); } else if ( cleartextData ) { mRawReplyString += *cleartextData; if ( mReader ) { - htmlWriter()->queue( quotedHTML( cleartextData->data(), true ) ); + if (!aCodec) { + htmlWriter()->queue( quotedHTML( cleartextData->data(), true ) ); + } else { + htmlWriter()->queue( quotedHTML( aCodec->toUnicode( cleartextData->data() ), true ) ); + } } } if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } else if ( !hideErrors ) { QString txt; txt = "

"; txt.append( i18n( "The crypto engine returned no cleartext data." ) ); txt.append( "

" ); txt.append( "
 
" ); txt.append( i18n( "Status: " ) ); if ( !messagePart.status.isEmpty() ) { txt.append( "" ); txt.append( messagePart.status ); txt.append( "" ); } else txt.append( i18n("(unknown)") ); if ( mReader ) htmlWriter()->queue(txt); } } else { if ( mReader ) { if ( !cryptProto ) { QString errorMsg; switch ( cryptPlugError ) { case NOT_INITIALIZED: errorMsg = i18n( "Crypto plug-in \"%1\" is not initialized." ) .arg( cryptPlugLibName ); break; case CANT_VERIFY_SIGNATURES: errorMsg = i18n( "Crypto plug-in \"%1\" cannot verify signatures." ) .arg( cryptPlugLibName ); break; case NO_PLUGIN: if ( cryptPlugDisplayName.isEmpty() ) errorMsg = i18n( "No appropriate crypto plug-in was found." ); else errorMsg = i18n( "%1 is either 'OpenPGP' or 'S/MIME'", "No %1 plug-in was found." ) .arg( cryptPlugDisplayName ); break; } messagePart.errorText = i18n( "The message is signed, but the " "validity of the signature cannot be " "verified.
" "Reason: %1" ) .arg( errorMsg ); } if ( mReader ) htmlWriter()->queue( writeSigstatHeader( messagePart, cryptProto, fromAddress ) ); } ObjectTreeParser otp( mReader, cryptProto, true ); otp.parseObjectTree( data ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: done, returning " // << ( bIsOpaqueSigned ? "TRUE" : "FALSE" ) << endl; return bIsOpaqueSigned; } void ObjectTreeParser::writeDecryptionInProgressBlock() { assert( mReader ); // PENDING(marc) find an animated icon here: //const QString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small ); const QString decryptedData = i18n("Encrypted data not shown"); PartMetaData messagePart; messagePart.isDecryptable = true; messagePart.isEncrypted = true; messagePart.isSigned = false; messagePart.inProgress = true; htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), QString() ) ); //htmlWriter()->queue( decryptedData ); htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } void ObjectTreeParser::writeDeferredDecryptionBlock() { assert( mReader ); const QString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small ); const QString decryptedData = "
" + i18n("This message is encrypted.") + "
" "
"; PartMetaData messagePart; messagePart.isDecryptable = true; messagePart.isEncrypted = true; messagePart.isSigned = false; mRawReplyString += decryptedData.utf8(); htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), QString() ) ); htmlWriter()->queue( decryptedData ); htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } bool ObjectTreeParser::okDecryptMIME( partNode& data, QCString& decryptedData, bool& signatureFound, std::vector &signatures, bool showWarning, bool& passphraseError, bool& actuallyEncrypted, bool& decryptionStarted, QString& aErrorText, GpgME::Error & auditLogError, QString& auditLog ) { passphraseError = false; decryptionStarted = false; aErrorText = QString::null; auditLogError = GpgME::Error(); auditLog = QString::null; bool bDecryptionOk = false; enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT } cryptPlugError = NO_PLUGIN; const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol(); QString cryptPlugLibName; if ( cryptProto ) cryptPlugLibName = cryptProto->name(); assert( !mReader || mReader->decryptMessage() ); if ( cryptProto && !kmkernel->contextMenuShown() ) { QByteArray ciphertext( data.msgPart().bodyDecodedBinary() ); #ifdef MARCS_DEBUG QCString cipherStr( ciphertext.data(), ciphertext.size() + 1 ); bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) && (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) && (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) ); dumpToFile( "dat_04_reader.encrypted", ciphertext.data(), ciphertext.size() ); QCString deb; deb = "\n\nE N C R Y P T E D D A T A = "; if ( cipherIsBinary ) deb += "[binary data]"; else { deb += "\""; deb += cipherStr; deb += "\""; } deb += "\n\n"; kdDebug(5006) << deb << endl; #endif //kdDebug(5006) << "ObjectTreeParser::decryptMIME: going to call CRYPTPLUG " // << cryptPlugLibName << endl; if ( mReader ) emit mReader->noDrag(); // in case pineentry pops up, don't let kmheaders start a drag afterwards // Check whether the memento contains a result from last time: const DecryptVerifyBodyPartMemento * m = dynamic_cast( data.bodyPartMemento( "decryptverify" ) ); if ( !m ) { Kleo::DecryptVerifyJob * job = cryptProto->decryptVerifyJob(); if ( !job ) { cryptPlugError = CANT_DECRYPT; cryptProto = 0; } else { DecryptVerifyBodyPartMemento * newM = new DecryptVerifyBodyPartMemento( job, ciphertext ); if ( allowAsync() ) { if ( newM->start() ) { decryptionStarted = true; mHasPendingAsyncJobs = true; } else { m = newM; } } else { newM->exec(); m = newM; } data.setBodyPartMemento( "decryptverify", newM ); } } else if ( m->isRunning() ) { decryptionStarted = true; mHasPendingAsyncJobs = true; m = 0; } if ( m ) { const QByteArray & plainText = m->plainText(); const GpgME::DecryptionResult & decryptResult = m->decryptResult(); const GpgME::VerificationResult & verifyResult = m->verifyResult(); std::stringstream ss; ss << decryptResult << '\n' << verifyResult; kdDebug(5006) << ss.str().c_str() << endl; signatureFound = verifyResult.signatures().size() > 0; signatures = verifyResult.signatures(); bDecryptionOk = !decryptResult.error(); passphraseError = decryptResult.error().isCanceled() || decryptResult.error().code() == GPG_ERR_NO_SECKEY; actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; aErrorText = QString::fromLocal8Bit( decryptResult.error().asString() ); auditLogError = m->auditLogError(); auditLog = m->auditLogAsHtml(); //kdDebug(5006) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG" // << endl; if ( bDecryptionOk ) decryptedData = QCString( plainText.data(), plainText.size() + 1 ); else if ( mReader && showWarning ) { decryptedData = "
" + i18n("Encrypted data not shown.").utf8() + "
"; if ( !passphraseError ) aErrorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.") .arg( cryptPlugLibName ) + "
" + i18n("Error: %1").arg( aErrorText ); } } } if ( !cryptProto ) { decryptedData = "
" + i18n("Encrypted data not shown.").utf8() + "
"; switch ( cryptPlugError ) { case NOT_INITIALIZED: aErrorText = i18n( "Crypto plug-in \"%1\" is not initialized." ) .arg( cryptPlugLibName ); break; case CANT_DECRYPT: aErrorText = i18n( "Crypto plug-in \"%1\" cannot decrypt messages." ) .arg( cryptPlugLibName ); break; case NO_PLUGIN: aErrorText = i18n( "No appropriate crypto plug-in was found." ); break; } } else if ( kmkernel->contextMenuShown() ) { // ### Workaround for bug 56693 (kmail freeze with the complete desktop // ### while pinentry-qt appears) QByteArray ciphertext( data.msgPart().bodyDecodedBinary() ); QCString cipherStr( ciphertext.data(), ciphertext.size() + 1 ); bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) && (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) && (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) ); if ( !cipherIsBinary ) { decryptedData = cipherStr; } else { decryptedData = "
" + i18n("Encrypted data not shown.").utf8() + "
"; } } dumpToFile( "dat_05_reader.decrypted", decryptedData.data(), decryptedData.size() ); return bDecryptionOk; } //static bool ObjectTreeParser::containsExternalReferences( const QCString & str ) { QRegExp httpRegExp("(\\\"|\\\'|url\\s*\\(\\s*)http[s]?:"); int httpPos = str.find( httpRegExp, 0 ); while ( httpPos >= 0 ) { // look backwards for "href" if ( httpPos > 5 ) { int hrefPos = str.findRev( "href", httpPos - 5, true ); // if no 'href' is found or the distance between 'href' and '"http[s]:' // is larger than 7 (7 is the distance in 'href = "http[s]:') then // we assume that we have found an external reference if ( ( hrefPos == -1 ) || ( httpPos - hrefPos > 7 ) ) return true; } // find next occurrence of "http: or "https: httpPos = str.find( httpRegExp, httpPos + 6 ); } return false; } bool ObjectTreeParser::processTextHtmlSubtype( partNode * curNode, ProcessResult & ) { QCString cstr( curNode->msgPart().bodyDecoded() ); mRawReplyString = cstr; if ( curNode->isFirstTextPart() ) { mTextualContent += curNode->msgPart().bodyToUnicode(); mTextualContentCharset = curNode->msgPart().charset(); } if ( !mReader ) return true; if ( curNode->isFirstTextPart() || attachmentStrategy()->defaultDisplay( curNode ) == AttachmentStrategy::Inline || showOnlyOneMimePart() ) { if ( mReader->htmlMail() ) { curNode->setDisplayedEmbedded( true ); // ---Sven's strip and from end of attachment start- // We must fo this, or else we will see only 1st inlined html // attachment. It is IMHO enough to search only for and // put \0 there. int i = cstr.findRev("", -1, false); //case insensitive if ( 0 <= i ) cstr.truncate(i); else // just in case - search for { i = cstr.findRev("", -1, false); //case insensitive if ( 0 <= i ) cstr.truncate(i); } // ---Sven's strip and from end of attachment end- // Show the "external references" warning (with possibility to load // external references only if loading external references is disabled // and the HTML code contains obvious external references). For // messages where the external references are obfuscated the user won't // have an easy way to load them but that shouldn't be a problem // because only spam contains obfuscated external references. if ( !mReader->htmlLoadExternal() && containsExternalReferences( cstr ) ) { htmlWriter()->queue( "
\n" ); htmlWriter()->queue( i18n("Note: This HTML message may contain external " "references to images etc. For security/privacy reasons " "external references are not loaded. If you trust the " "sender of this message then you can load the external " "references for this message " "by clicking here.") ); htmlWriter()->queue( "


" ); } } else { htmlWriter()->queue( "
\n" ); htmlWriter()->queue( i18n("Note: This is an HTML message. For " "security reasons, only the raw HTML code " "is shown. If you trust the sender of this " "message then you can activate formatted " "HTML display for this message " "by clicking here.") ); htmlWriter()->queue( "


" ); } htmlWriter()->queue( codecFor( curNode )->toUnicode( mReader->htmlMail() ? cstr : KMMessage::html2source( cstr ))); mReader->mColorBar->setHtmlMode(); return true; } return false; } } // namespace KMail static bool isMailmanMessage( partNode * curNode ) { if ( !curNode->dwPart() || !curNode->dwPart()->hasHeaders() ) return false; DwHeaders & headers = curNode->dwPart()->Headers(); if ( headers.HasField("X-Mailman-Version") ) return true; if ( headers.HasField("X-Mailer") && 0 == QCString( headers.FieldBody("X-Mailer").AsString().c_str() ) .find("MAILMAN", 0, false) ) return true; return false; } namespace KMail { bool ObjectTreeParser::processMailmanMessage( partNode * curNode ) { const QCString cstr = curNode->msgPart().bodyDecoded(); //### const QCString delim1( "--__--__--\n\nMessage:"); const QCString delim2( "--__--__--\r\n\r\nMessage:"); const QCString delimZ2("--__--__--\n\n_____________"); const QCString delimZ1("--__--__--\r\n\r\n_____________"); QCString partStr, digestHeaderStr; int thisDelim = cstr.find(delim1, 0, false); if ( thisDelim == -1 ) thisDelim = cstr.find(delim2, 0, false); if ( thisDelim == -1 ) { kdDebug(5006) << " Sorry: Old style Mailman message but no delimiter found." << endl; return false; } int nextDelim = cstr.find(delim1, thisDelim+1, false); if ( -1 == nextDelim ) nextDelim = cstr.find(delim2, thisDelim+1, false); if ( -1 == nextDelim ) nextDelim = cstr.find(delimZ1, thisDelim+1, false); if ( -1 == nextDelim ) nextDelim = cstr.find(delimZ2, thisDelim+1, false); if ( nextDelim < 0) return false; //kdDebug(5006) << " processing old style Mailman digest" << endl; //if ( curNode->mRoot ) // curNode = curNode->mRoot; // at least one message found: build a mime tree digestHeaderStr = "Content-Type=text/plain\nContent-Description=digest header\n\n"; digestHeaderStr += cstr.mid( 0, thisDelim ); insertAndParseNewChildNode( *curNode, &*digestHeaderStr, "Digest Header", true ); //mReader->queueHtml("


"); // temporarily change curent node's Content-Type // to get our embedded RfC822 messages properly inserted curNode->setType( DwMime::kTypeMultipart ); curNode->setSubType( DwMime::kSubtypeDigest ); while( -1 < nextDelim ){ int thisEoL = cstr.find("\nMessage:", thisDelim, false); if ( -1 < thisEoL ) thisDelim = thisEoL+1; else{ thisEoL = cstr.find("\n_____________", thisDelim, false); if ( -1 < thisEoL ) thisDelim = thisEoL+1; } thisEoL = cstr.find('\n', thisDelim); if ( -1 < thisEoL ) thisDelim = thisEoL+1; else thisDelim = thisDelim+1; //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] ) // ++thisDelim; partStr = "Content-Type=message/rfc822\nContent-Description=embedded message\n"; partStr += cstr.mid( thisDelim, nextDelim-thisDelim ); QCString subject("embedded message"); QCString subSearch("\nSubject:"); int subPos = partStr.find(subSearch, 0, false); if ( -1 < subPos ){ subject = partStr.mid(subPos+subSearch.length()); thisEoL = subject.find('\n'); if ( -1 < thisEoL ) subject.truncate( thisEoL ); } //kdDebug(5006) << " embedded message found: \"" << subject << "\"" << endl; insertAndParseNewChildNode( *curNode, &*partStr, subject, true ); //mReader->queueHtml("


"); thisDelim = nextDelim+1; nextDelim = cstr.find(delim1, thisDelim, false); if ( -1 == nextDelim ) nextDelim = cstr.find(delim2, thisDelim, false); if ( -1 == nextDelim ) nextDelim = cstr.find(delimZ1, thisDelim, false); if ( -1 == nextDelim ) nextDelim = cstr.find(delimZ2, thisDelim, false); } // reset curent node's Content-Type curNode->setType( DwMime::kTypeText ); curNode->setSubType( DwMime::kSubtypePlain ); int thisEoL = cstr.find("_____________", thisDelim); if ( -1 < thisEoL ){ thisDelim = thisEoL; thisEoL = cstr.find('\n', thisDelim); if ( -1 < thisEoL ) thisDelim = thisEoL+1; } else thisDelim = thisDelim+1; partStr = "Content-Type=text/plain\nContent-Description=digest footer\n\n"; partStr += cstr.mid( thisDelim ); insertAndParseNewChildNode( *curNode, &*partStr, "Digest Footer", true ); return true; } bool ObjectTreeParser::processTextPlainSubtype( partNode * curNode, ProcessResult & result ) { if ( !mReader ) { mRawReplyString = curNode->msgPart().bodyDecoded(); if ( curNode->isFirstTextPart() ) { mTextualContent += curNode->msgPart().bodyToUnicode(); mTextualContentCharset = curNode->msgPart().charset(); } return true; } if ( !curNode->isFirstTextPart() && attachmentStrategy()->defaultDisplay( curNode ) != AttachmentStrategy::Inline && !showOnlyOneMimePart() ) return false; mRawReplyString = curNode->msgPart().bodyDecoded(); if ( curNode->isFirstTextPart() ) { mTextualContent += curNode->msgPart().bodyToUnicode(); mTextualContentCharset = curNode->msgPart().charset(); } const KConfigGroup reader( KMKernel::config(), "Reader" ); if ( mIsHTMLAlternativeAvailable && reader.readBoolEntry( "plainWithOption", false ) ) { htmlWriter()->queue( "
\n" ); htmlWriter()->queue( i18n("Note: This message has an alternative " "HTML representation. If you trust the sender of this " "message then you can activate formatted " "HTML display for this message " "by clicking here.") ); htmlWriter()->queue( "


" ); mIsHTMLAlternativeAvailable = false; // ensure that this is shown only once. } QString label = curNode->msgPart().fileName().stripWhiteSpace(); if ( label.isEmpty() ) label = curNode->msgPart().name().stripWhiteSpace(); const bool bDrawFrame = !curNode->isFirstTextPart() && !showOnlyOneMimePart() && !label.isEmpty(); if ( bDrawFrame ) { label = KMMessage::quoteHtmlChars( label, true ); const QString comment = KMMessage::quoteHtmlChars( curNode->msgPart().contentDescription(), true ); const QString fileName = mReader->writeMessagePartToTempFile( &curNode->msgPart(), curNode->nodeId() ); const QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ; QString htmlStr = "" "
"; if ( !fileName.isEmpty() ) htmlStr += "asHREF( "body" ) + "\">" + label + ""; else htmlStr += label; if ( !comment.isEmpty() ) htmlStr += "
" + comment; htmlStr += "
"; htmlWriter()->queue( htmlStr ); } // process old style not-multipart Mailman messages to // enable verification of the embedded messages' signatures if ( !isMailmanMessage( curNode ) || !processMailmanMessage( curNode ) ) { writeBodyString( mRawReplyString, curNode->trueFromAddress(), codecFor( curNode ), result, !bDrawFrame ); curNode->setDisplayedEmbedded( true ); } if ( bDrawFrame ) htmlWriter()->queue( "
" ); return true; } void ObjectTreeParser::stdChildHandling( partNode * child ) { if ( !child ) return; ObjectTreeParser otp( *this ); otp.setShowOnlyOneMimePart( false ); otp.parseObjectTree( child ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); } QString ObjectTreeParser::defaultToltecReplacementText() { return i18n( "This message is a Toltec Groupware object, it can only be viewed with " "Microsoft Outlook in combination with the Toltec connector." ); } bool ObjectTreeParser::processToltecMail( partNode *node ) { if ( !node || !mHtmlWriter || !GlobalSettings::self()->showToltecReplacementText() || !node->isToltecMessage() || mShowRawToltecMail ) return false; htmlWriter()->queue( GlobalSettings::self()->toltecReplacementText() ); htmlWriter()->queue( "

" + i18n( "Show Raw Message" ) + "" ); return true; } bool ObjectTreeParser::processMultiPartMixedSubtype( partNode * node, ProcessResult & ) { if ( processToltecMail( node ) ) { return true; } partNode * child = node->firstChild(); if ( !child ) return false; // normal treatment of the parts in the mp/mixed container stdChildHandling( child ); return true; } bool ObjectTreeParser::processMultiPartAlternativeSubtype( partNode * node, ProcessResult & ) { partNode * child = node->firstChild(); if ( !child ) return false; partNode * dataHtml = child->findType( DwMime::kTypeText, DwMime::kSubtypeHtml, false, true ); partNode * dataPlain = child->findType( DwMime::kTypeText, DwMime::kSubtypePlain, false, true ); if ( (mReader && mReader->htmlMail() && dataHtml) || (dataHtml && dataPlain && dataPlain->msgPart().body().isEmpty()) ) { if ( dataPlain ) dataPlain->setProcessed( true, false ); stdChildHandling( dataHtml ); return true; } if ( !mReader || (!mReader->htmlMail() && dataPlain) ) { if ( dataHtml ) { dataHtml->setProcessed( true, false ); mIsHTMLAlternativeAvailable = true; } stdChildHandling( dataPlain ); return true; } stdChildHandling( child ); return true; } bool ObjectTreeParser::processMultiPartDigestSubtype( partNode * node, ProcessResult & result ) { return processMultiPartMixedSubtype( node, result ); } bool ObjectTreeParser::processMultiPartParallelSubtype( partNode * node, ProcessResult & result ) { return processMultiPartMixedSubtype( node, result ); } bool ObjectTreeParser::processMultiPartSignedSubtype( partNode * node, ProcessResult & ) { if ( node->childCount() != 2 ) { kdDebug(5006) << "mulitpart/signed must have exactly two child parts!" << endl << "processing as multipart/mixed" << endl; if ( node->firstChild() ) stdChildHandling( node->firstChild() ); return node->firstChild(); } partNode * signedData = node->firstChild(); assert( signedData ); partNode * signature = signedData->nextSibling(); assert( signature ); signature->setProcessed( true, true ); if ( !includeSignatures() ) { stdChildHandling( signedData ); return true; } // FIXME(marc) check here that the protocol parameter matches the // mimetype of "signature" (not required by the RFC, but practised // by all implementaions of security multiparts const QString contentType = node->contentTypeParameter( "protocol" ).lower(); const Kleo::CryptoBackend::Protocol *protocol = 0; if ( contentType == "application/pkcs7-signature" || contentType == "application/x-pkcs7-signature" ) protocol = Kleo::CryptoBackendFactory::instance()->smime(); else if ( contentType == "application/pgp-signature" || contentType == "application/x-pgp-signature" ) protocol = Kleo::CryptoBackendFactory::instance()->openpgp(); if ( !protocol ) { signature->setProcessed( true, true ); stdChildHandling( signedData ); return true; } CryptoProtocolSaver saver( this, protocol ); node->setSignatureState( KMMsgFullySigned ); writeOpaqueOrMultipartSignedData( signedData, *signature, node->trueFromAddress() ); return true; } bool ObjectTreeParser::processMultiPartEncryptedSubtype( partNode * node, ProcessResult & result ) { partNode * child = node->firstChild(); if ( !child ) return false; if ( keepEncryptions() ) { node->setEncryptionState( KMMsgFullyEncrypted ); const QCString cstr = node->msgPart().bodyDecoded(); if ( mReader ) writeBodyString( cstr, node->trueFromAddress(), codecFor( node ), result, false ); mRawReplyString += cstr; return true; } const Kleo::CryptoBackend::Protocol * useThisCryptProto = 0; /* ATTENTION: This code is to be replaced by the new 'auto-detect' feature. -------------------------------------- */ partNode * data = child->findType( DwMime::kTypeApplication, DwMime::kSubtypeOctetStream, false, true ); if ( data ) { useThisCryptProto = Kleo::CryptoBackendFactory::instance()->openpgp(); } if ( !data ) { data = child->findType( DwMime::kTypeApplication, DwMime::kSubtypePkcs7Mime, false, true ); if ( data ) { useThisCryptProto = Kleo::CryptoBackendFactory::instance()->smime(); } } /* --------------------------------------------------------------------------------------------------------------- */ if ( !data ) { stdChildHandling( child ); return true; } CryptoProtocolSaver cpws( this, useThisCryptProto ); if ( partNode * dataChild = data->firstChild() ) { //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; stdChildHandling( dataChild ); //kdDebug(5006) << "\n-----> Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } node->setEncryptionState( KMMsgFullyEncrypted ); if ( mReader && !mReader->decryptMessage() ) { writeDeferredDecryptionBlock(); data->setProcessed( true, false ); // Set the data node to done to prevent it from being processed return true; } //kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl; PartMetaData messagePart; QCString decryptedData; bool signatureFound; std::vector signatures; bool passphraseError; bool actuallyEncrypted = true; bool decryptionStarted; bool bOkDecrypt = okDecryptMIME( *data, decryptedData, signatureFound, signatures, true, passphraseError, actuallyEncrypted, decryptionStarted, messagePart.errorText, messagePart.auditLogError, messagePart.auditLog ); if ( decryptionStarted ) { writeDecryptionInProgressBlock(); return true; } // paint the frame if ( mReader ) { messagePart.isDecryptable = bOkDecrypt; messagePart.isEncrypted = true; messagePart.isSigned = false; htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), node->trueFromAddress() ) ); } if ( bOkDecrypt ) { // Note: Multipart/Encrypted might also be signed // without encapsulating a nicely formatted // ~~~~~~~ Multipart/Signed part. // (see RFC 3156 --> 6.2) // In this case we paint a _2nd_ frame inside the // encryption frame, but we do _not_ show a respective // encapsulated MIME part in the Mime Tree Viewer // since we do want to show the _true_ structure of the // message there - not the structure that the sender's // MUA 'should' have sent. :-D (khz, 12.09.2002) // if ( signatureFound ) { writeOpaqueOrMultipartSignedData( 0, *node, node->trueFromAddress(), false, &decryptedData, signatures, false ); node->setSignatureState( KMMsgFullySigned ); } else { insertAndParseNewChildNode( *node, &*decryptedData, "encrypted data" ); } } else { mRawReplyString += decryptedData; if ( mReader ) { // print the error message that was returned in decryptedData // (utf8-encoded) htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) ); } } if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); data->setProcessed( true, false ); // Set the data node to done to prevent it from being processed return true; } bool ObjectTreeParser::processMessageRfc822Subtype( partNode * node, ProcessResult & ) { if ( mReader && !attachmentStrategy()->inlineNestedMessages() && !showOnlyOneMimePart() ) return false; if ( partNode * child = node->firstChild() ) { //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.parseObjectTree( child ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } //kdDebug(5006) << "\n-----> Initially processing data of embedded RfC 822 message\n" << endl; // paint the frame PartMetaData messagePart; if ( mReader ) { messagePart.isEncrypted = false; messagePart.isSigned = false; messagePart.isEncapsulatedRfc822Message = true; QString filename = mReader->writeMessagePartToTempFile( &node->msgPart(), node->nodeId() ); htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), node->trueFromAddress(), node ) ); } QCString rfc822messageStr( node->msgPart().bodyDecoded() ); // display the headers of the encapsulated message DwMessage* rfc822DwMessage = new DwMessage(); // will be deleted by c'tor of rfc822headers rfc822DwMessage->FromString( rfc822messageStr ); rfc822DwMessage->Parse(); KMMessage rfc822message( rfc822DwMessage ); node->setFromAddress( rfc822message.from() ); //kdDebug(5006) << "\n-----> Store RfC 822 message header \"From: " << rfc822message.from() << "\"\n" << endl; if ( mReader ) htmlWriter()->queue( mReader->writeMsgHeader( &rfc822message ) ); //mReader->parseMsgHeader( &rfc822message ); // display the body of the encapsulated message if ( mRFC822RecursionCnt < MAX_RFC822_EMBEDD_DEPTH ) { kdDebug() << "Insert an parse child node " << mRFC822RecursionCnt << endl; insertAndParseNewChildNode( *node, &*rfc822messageStr, "encapsulated message", false /*append*/, false /*add to textual content*/ ); node->setDisplayedEmbedded( true ); } else { htmlWriter()->queue( "
\n" ); htmlWriter()->queue( i18n("Warning: This Message contains too many levels of " "embedded messages. For performance reasons " "no more levels are shown. Please refer to the message " "structure window to access other parts of the message." ) ); htmlWriter()->queue( "


" ); } if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); return true; } bool ObjectTreeParser::processApplicationOctetStreamSubtype( partNode * node, ProcessResult & result ) { if ( partNode * child = node->firstChild() ) { //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.parseObjectTree( child ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } const Kleo::CryptoBackend::Protocol* oldUseThisCryptPlug = cryptoProtocol(); if ( node->parentNode() && DwMime::kTypeMultipart == node->parentNode()->type() && DwMime::kSubtypeEncrypted == node->parentNode()->subType() ) { //kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl; node->setEncryptionState( KMMsgFullyEncrypted ); if ( keepEncryptions() ) { const QCString cstr = node->msgPart().bodyDecoded(); if ( mReader ) writeBodyString( cstr, node->trueFromAddress(), codecFor( node ), result, false ); mRawReplyString += cstr; } else if ( mReader && !mReader->decryptMessage() ) { writeDeferredDecryptionBlock(); } else { /* ATTENTION: This code is to be replaced by the planned 'auto-detect' feature. */ PartMetaData messagePart; setCryptoProtocol( Kleo::CryptoBackendFactory::instance()->openpgp() ); QCString decryptedData; bool signatureFound; std::vector signatures; bool passphraseError; bool actuallyEncrypted = true; bool decryptionStarted; bool bOkDecrypt = okDecryptMIME( *node, decryptedData, signatureFound, signatures, true, passphraseError, actuallyEncrypted, decryptionStarted, messagePart.errorText, messagePart.auditLogError, messagePart.auditLog ); if ( decryptionStarted ) { writeDecryptionInProgressBlock(); return true; } // paint the frame if ( mReader ) { messagePart.isDecryptable = bOkDecrypt; messagePart.isEncrypted = true; messagePart.isSigned = false; htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), node->trueFromAddress() ) ); } if ( bOkDecrypt ) { // fixing the missing attachments bug #1090-b insertAndParseNewChildNode( *node, &*decryptedData, "encrypted data" ); } else { mRawReplyString += decryptedData; if ( mReader ) { // print the error message that was returned in decryptedData // (utf8-encoded) htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) ); } } if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } return true; } setCryptoProtocol( oldUseThisCryptPlug ); return false; } bool ObjectTreeParser::processApplicationPkcs7MimeSubtype( partNode * node, ProcessResult & result ) { if ( partNode * child = node->firstChild() ) { //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.parseObjectTree( child ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } //kdDebug(5006) << "\n-----> Initially processing signed and/or encrypted data\n" << endl; if ( !node->dwPart() || !node->dwPart()->hasHeaders() ) return false; const Kleo::CryptoBackend::Protocol * smimeCrypto = Kleo::CryptoBackendFactory::instance()->smime(); const QString smimeType = node->contentTypeParameter("smime-type").lower(); if ( smimeType == "certs-only" ) { result.setNeverDisplayInline( true ); if ( !smimeCrypto || !mReader ) return false; const KConfigGroup reader( KMKernel::config(), "Reader" ); if ( !reader.readBoolEntry( "AutoImportKeys", false ) ) return false; const QByteArray certData = node->msgPart().bodyDecodedBinary(); const STD_NAMESPACE_PREFIX auto_ptr import( smimeCrypto->importJob() ); const GpgME::ImportResult res = import->exec( certData ); if ( res.error() ) { htmlWriter()->queue( i18n( "Sorry, certificate could not be imported.
" "Reason: %1").arg( QString::fromLocal8Bit( res.error().asString() ) ) ); return true; } const int nImp = res.numImported(); const int nUnc = res.numUnchanged(); const int nSKImp = res.numSecretKeysImported(); const int nSKUnc = res.numSecretKeysUnchanged(); if ( !nImp && !nSKImp && !nUnc && !nSKUnc ) { htmlWriter()->queue( i18n( "Sorry, no certificates were found in this message." ) ); return true; } QString comment = "" + i18n( "Certificate import status:" ) + "
 
"; if ( nImp ) comment += i18n( "1 new certificate was imported.", "%n new certificates were imported.", nImp ) + "
"; if ( nUnc ) comment += i18n( "1 certificate was unchanged.", "%n certificates were unchanged.", nUnc ) + "
"; if ( nSKImp ) comment += i18n( "1 new secret key was imported.", "%n new secret keys were imported.", nSKImp ) + "
"; if ( nSKUnc ) comment += i18n( "1 secret key was unchanged.", "%n secret keys were unchanged.", nSKUnc ) + "
"; comment += " 
"; htmlWriter()->queue( comment ); if ( !nImp && !nSKImp ) { htmlWriter()->queue( "
" ); return true; } const std::vector imports = res.imports(); if ( imports.empty() ) { htmlWriter()->queue( i18n( "Sorry, no details on certificate import available." ) + "
" ); return true; } htmlWriter()->queue( "" + i18n( "Certificate import details:" ) + "
" ); for ( std::vector::const_iterator it = imports.begin() ; it != imports.end() ; ++it ) { if ( (*it).error() ) htmlWriter()->queue( i18n( "Failed: %1 (%2)" ) .arg( (*it).fingerprint(), QString::fromLocal8Bit( (*it).error().asString() ) ) ); else if ( (*it).status() & ~GpgME::Import::ContainedSecretKey ) { if ( (*it).status() & GpgME::Import::ContainedSecretKey ) { htmlWriter()->queue( i18n( "New or changed: %1 (secret key available)" ).arg( (*it).fingerprint() ) ); } else { htmlWriter()->queue( i18n( "New or changed: %1" ).arg( (*it).fingerprint() ) ); } } htmlWriter()->queue( "
" ); } htmlWriter()->queue( "
" ); return true; } if ( !smimeCrypto ) return false; CryptoProtocolSaver cpws( this, smimeCrypto ); bool isSigned = smimeType == "signed-data"; bool isEncrypted = smimeType == "enveloped-data"; // Analyze "signTestNode" node to find/verify a signature. // If zero this verification was successfully done after // decrypting via recursion by insertAndParseNewChildNode(). partNode* signTestNode = isEncrypted ? 0 : node; // We try decrypting the content // if we either *know* that it is an encrypted message part // or there is neither signed nor encrypted parameter. if ( !isSigned ) { if ( isEncrypted ) { //kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data" << endl; } else { //kdDebug(5006) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?" << endl; } QCString decryptedData; PartMetaData messagePart; messagePart.isEncrypted = true; messagePart.isSigned = false; bool signatureFound; std::vector signatures; bool passphraseError; bool actuallyEncrypted = true; bool decryptionStarted; if ( mReader && !mReader->decryptMessage() ) { writeDeferredDecryptionBlock(); isEncrypted = true; signTestNode = 0; // PENDING(marc) to be abs. sure, we'd need to have to look at the content } else { const bool bOkDecrypt = okDecryptMIME( *node, decryptedData, signatureFound, signatures, false, passphraseError, actuallyEncrypted, decryptionStarted, messagePart.errorText, messagePart.auditLogError, messagePart.auditLog ); if ( decryptionStarted ) { writeDecryptionInProgressBlock(); return true; } if ( bOkDecrypt ) { //kdDebug(5006) << "pkcs7 mime - encryption found - enveloped (encrypted) data !" << endl; isEncrypted = true; node->setEncryptionState( KMMsgFullyEncrypted ); signTestNode = 0; // paint the frame messagePart.isDecryptable = true; if ( mReader ) htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), node->trueFromAddress() ) ); insertAndParseNewChildNode( *node, &*decryptedData, "encrypted data" ); if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } else { // decryption failed, which could be because the part was encrypted but // decryption failed, or because we didn't know if it was encrypted, tried, // and failed. If the message was not actually encrypted, we continue // assuming it's signed if ( passphraseError || ( smimeType.isEmpty() && actuallyEncrypted ) ) { isEncrypted = true; signTestNode = 0; } if ( isEncrypted ) { //kdDebug(5006) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !" << endl; // paint the frame messagePart.isDecryptable = false; if ( mReader ) { htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), node->trueFromAddress() ) ); assert( mReader->decryptMessage() ); // handled above writePartIcon( &node->msgPart(), node->nodeId() ); htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } } else { //kdDebug(5006) << "pkcs7 mime - NO encryption found" << endl; } } } if ( isEncrypted ) node->setEncryptionState( KMMsgFullyEncrypted ); } // We now try signature verification if necessarry. if ( signTestNode ) { if ( isSigned ) { //kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: opaque signed data" << endl; } else { //kdDebug(5006) << "pkcs7 mime - type unknown - opaque signed data ?" << endl; } bool sigFound = writeOpaqueOrMultipartSignedData( 0, *signTestNode, node->trueFromAddress(), true, 0, std::vector(), isEncrypted ); if ( sigFound ) { if ( !isSigned ) { //kdDebug(5006) << "pkcs7 mime - signature found - opaque signed data !" << endl; isSigned = true; } signTestNode->setSignatureState( KMMsgFullySigned ); if ( signTestNode != node ) node->setSignatureState( KMMsgFullySigned ); } else { //kdDebug(5006) << "pkcs7 mime - NO signature found :-(" << endl; } } return isSigned || isEncrypted; } bool ObjectTreeParser::decryptChiasmus( const QByteArray& data, QByteArray& bodyDecoded, QString& errorText ) { const Kleo::CryptoBackend::Protocol * chiasmus = Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" ); Q_ASSERT( chiasmus ); if ( !chiasmus ) return false; const STD_NAMESPACE_PREFIX auto_ptr listjob( chiasmus->specialJob( "x-obtain-keys", QMap() ) ); if ( !listjob.get() ) { errorText = i18n( "Chiasmus backend does not offer the " "\"x-obtain-keys\" function. Please report this bug." ); return false; } if ( listjob->exec() ) { errorText = i18n( "Chiasmus Backend Error" ); return false; } const QVariant result = listjob->property( "result" ); if ( result.type() != QVariant::StringList ) { errorText = i18n( "Unexpected return value from Chiasmus backend: " "The \"x-obtain-keys\" function did not return a " "string list. Please report this bug." ); return false; } const QStringList keys = result.toStringList(); if ( keys.empty() ) { errorText = i18n( "No keys have been found. Please check that a " "valid key path has been set in the Chiasmus " "configuration." ); return false; } emit mReader->noDrag(); ChiasmusKeySelector selectorDlg( mReader, i18n( "Chiasmus Decryption Key Selection" ), keys, GlobalSettings::chiasmusDecryptionKey(), GlobalSettings::chiasmusDecryptionOptions() ); if ( selectorDlg.exec() != QDialog::Accepted ) return false; GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() ); GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() ); assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() ); const STD_NAMESPACE_PREFIX auto_ptr job( chiasmus->specialJob( "x-decrypt", QMap() ) ); if ( !job.get() ) { errorText = i18n( "Chiasmus backend does not offer the " "\"x-decrypt\" function. Please report this bug." ); return false; } if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) || !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) || !job->setProperty( "input", data ) ) { errorText = i18n( "The \"x-decrypt\" function does not accept " "the expected parameters. Please report this bug." ); return false; } if ( job->exec() ) { errorText = i18n( "Chiasmus Decryption Error" ); return false; } const QVariant resultData = job->property( "result" ); if ( resultData.type() != QVariant::ByteArray ) { errorText = i18n( "Unexpected return value from Chiasmus backend: " "The \"x-decrypt\" function did not return a " "byte array. Please report this bug." ); return false; } bodyDecoded = resultData.toByteArray(); return true; } bool ObjectTreeParser::processApplicationChiasmusTextSubtype( partNode * curNode, ProcessResult & result ) { if ( !mReader ) { mRawReplyString = curNode->msgPart().bodyDecoded(); mTextualContent += curNode->msgPart().bodyToUnicode(); mTextualContentCharset = curNode->msgPart().charset(); return true; } QByteArray decryptedBody; QString errorText; const QByteArray data = curNode->msgPart().bodyDecodedBinary(); bool bOkDecrypt = decryptChiasmus( data, decryptedBody, errorText ); PartMetaData messagePart; messagePart.isDecryptable = bOkDecrypt; messagePart.isEncrypted = true; messagePart.isSigned = false; messagePart.errorText = errorText; if ( mReader ) htmlWriter()->queue( writeSigstatHeader( messagePart, 0, //cryptPlugWrapper(), curNode->trueFromAddress() ) ); const QByteArray body = bOkDecrypt ? decryptedBody : data; const QString chiasmusCharset = curNode->contentTypeParameter("chiasmus-charset"); const QTextCodec* aCodec = chiasmusCharset.isEmpty() ? codecFor( curNode ) : KMMsgBase::codecForName( chiasmusCharset.ascii() ); htmlWriter()->queue( quotedHTML( aCodec->toUnicode( body ), false /*decorate*/ ) ); result.setInlineEncryptionState( KMMsgFullyEncrypted ); if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); return true; } bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessResult &result ) { Q_UNUSED( result ); if ( !mReader ) return false; const QString fileName = mReader->writeMessagePartToTempFile( &node->msgPart(), node->nodeId() ); KTNEFParser parser; if ( !parser.openFile( fileName ) || !parser.message()) { kdDebug() << k_funcinfo << "Could not parse " << fileName << endl; return false; } QPtrList tnefatts = parser.message()->attachmentList(); if ( tnefatts.isEmpty() ) { kdDebug() << k_funcinfo << "No attachments found in " << fileName << endl; return false; } if ( !showOnlyOneMimePart() ) { QString label = node->msgPart().fileName().stripWhiteSpace(); if ( label.isEmpty() ) label = node->msgPart().name().stripWhiteSpace(); label = KMMessage::quoteHtmlChars( label, true ); const QString comment = KMMessage::quoteHtmlChars( node->msgPart().contentDescription(), true ); const QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ; QString htmlStr = "" "
"; if ( !fileName.isEmpty() ) htmlStr += "asHREF( "body" ) + "\">" + label + ""; else htmlStr += label; if ( !comment.isEmpty() ) htmlStr += "
" + comment; htmlStr += "
"; htmlWriter()->queue( htmlStr ); } for ( uint i = 0; i < tnefatts.count(); ++i ) { KTNEFAttach *att = tnefatts.at( i ); QString label = att->displayName(); if( label.isEmpty() ) label = att->name(); label = KMMessage::quoteHtmlChars( label, true ); QString dir = mReader->createTempDir( "ktnef-" + QString::number( i ) ); parser.extractFileTo( att->name(), dir ); mReader->mTempFiles.append( dir + QDir::separator() + att->name() ); QString href = "file:" + KURL::encode_string( dir + QDir::separator() + att->name() ); KMimeType::Ptr mimeType = KMimeType::mimeType( att->mimeTag() ); QString iconName = KGlobal::instance()->iconLoader()->iconPath( mimeType->icon( QString(), false ), KIcon::Desktop ); htmlWriter()->queue( "
" ); } if ( !showOnlyOneMimePart() ) htmlWriter()->queue( "
" ); return true; } void ObjectTreeParser::writeBodyString( const QCString & bodyString, const QString & fromAddress, const QTextCodec * codec, ProcessResult & result, bool decorate ) { assert( mReader ); assert( codec ); KMMsgSignatureState inlineSignatureState = result.inlineSignatureState(); KMMsgEncryptionState inlineEncryptionState = result.inlineEncryptionState(); writeBodyStr( bodyString, codec, fromAddress, inlineSignatureState, inlineEncryptionState, decorate ); result.setInlineSignatureState( inlineSignatureState ); result.setInlineEncryptionState( inlineEncryptionState ); } void ObjectTreeParser::writePartIcon( KMMessagePart * msgPart, int partNum, bool inlineImage ) { if ( !mReader || !msgPart ) return; QString label = msgPart->fileName(); if( label.isEmpty() ) label = msgPart->name(); if( label.isEmpty() ) label = "unnamed"; label = KMMessage::quoteHtmlChars( label, true ); QString comment = msgPart->contentDescription(); comment = KMMessage::quoteHtmlChars( comment, true ); if ( label == comment ) comment = QString::null; QString fileName = mReader->writeMessagePartToTempFile( msgPart, partNum ); QString href = QString( "attachment:%1?place=body" ).arg( partNum ); QString iconName; if( inlineImage ) iconName = href; else { iconName = msgPart->iconName(); if( iconName.right( 14 ) == "mime_empty.png" ) { msgPart->magicSetType(); iconName = msgPart->iconName(); } } QCString contentId = msgPart->contentId(); if ( !contentId.isEmpty() ) { htmlWriter()->embedPart( contentId, href ); } if( inlineImage ) // show the filename of the image below the embedded image htmlWriter()->queue( "
" "" "
" "" "
" + comment + "

" ); else // show the filename next to the icon htmlWriter()->queue( "" "
" + comment + "

" ); } #define SIG_FRAME_COL_UNDEF 99 #define SIG_FRAME_COL_RED -1 #define SIG_FRAME_COL_YELLOW 0 #define SIG_FRAME_COL_GREEN 1 QString ObjectTreeParser::sigStatusToString( const Kleo::CryptoBackend::Protocol* cryptProto, int status_code, GpgME::Signature::Summary summary, int& frameColor, bool& showKeyInfos ) { // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. showKeyInfos = true; QString result; if( cryptProto ) { if( cryptProto == Kleo::CryptoBackendFactory::instance()->openpgp() ) { // process enum according to it's definition to be read in // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h switch( status_code ) { case 0: // GPGME_SIG_STAT_NONE result = i18n("Error: Signature not verified"); break; case 1: // GPGME_SIG_STAT_GOOD result = i18n("Good signature"); break; case 2: // GPGME_SIG_STAT_BAD result = i18n("Bad signature"); break; case 3: // GPGME_SIG_STAT_NOKEY result = i18n("No public key to verify the signature"); break; case 4: // GPGME_SIG_STAT_NOSIG result = i18n("No signature found"); break; case 5: // GPGME_SIG_STAT_ERROR result = i18n("Error verifying the signature"); break; case 6: // GPGME_SIG_STAT_DIFF result = i18n("Different results for signatures"); break; /* PENDING(khz) Verify exact meaning of the following values: case 7: // GPGME_SIG_STAT_GOOD_EXP return i18n("Signature certificate is expired"); break; case 8: // GPGME_SIG_STAT_GOOD_EXPKEY return i18n("One of the certificate's keys is expired"); break; */ default: result = ""; // do *not* return a default text here ! break; } } else if ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() ) { // process status bits according to SigStatus_... // definitions in kdenetwork/libkdenetwork/cryptplug.h if( summary == GpgME::Signature::None ) { result = i18n("No status information available."); frameColor = SIG_FRAME_COL_YELLOW; showKeyInfos = false; return result; } if( summary & GpgME::Signature::Valid ) { result = i18n("Good signature."); // Note: // Here we are work differently than KMail did before! // // The GOOD case ( == sig matching and the complete // certificate chain was verified and is valid today ) // by definition does *not* show any key // information but just states that things are OK. // (khz, according to LinuxTag 2002 meeting) frameColor = SIG_FRAME_COL_GREEN; showKeyInfos = false; return result; } // we are still there? OK, let's test the different cases: // we assume green, test for yellow or red (in this order!) frameColor = SIG_FRAME_COL_GREEN; QString result2; if( summary & GpgME::Signature::KeyExpired ){ // still is green! result2 += i18n("One key has expired."); } if( summary & GpgME::Signature::SigExpired ){ // and still is green! result2 += i18n("The signature has expired."); } // test for yellow: if( summary & GpgME::Signature::KeyMissing ) { result2 += i18n("Unable to verify: key missing."); // if the signature certificate is missing // we cannot show infos on it showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } if( summary & GpgME::Signature::CrlMissing ){ result2 += i18n("CRL not available."); frameColor = SIG_FRAME_COL_YELLOW; } if( summary & GpgME::Signature::CrlTooOld ){ result2 += i18n("Available CRL is too old."); frameColor = SIG_FRAME_COL_YELLOW; } if( summary & GpgME::Signature::BadPolicy ){ result2 += i18n("A policy was not met."); frameColor = SIG_FRAME_COL_YELLOW; } if( summary & GpgME::Signature::SysError ){ result2 += i18n("A system error occurred."); // if a system error occurred // we cannot trust any information // that was given back by the plug-in showKeyInfos = false; frameColor = SIG_FRAME_COL_YELLOW; } // test for red: if( summary & GpgME::Signature::KeyRevoked ){ // this is red! result2 += i18n("One key has been revoked."); frameColor = SIG_FRAME_COL_RED; } if( summary & GpgME::Signature::Red ) { if( result2.isEmpty() ) // Note: // Here we are work differently than KMail did before! // // The BAD case ( == sig *not* matching ) // by definition does *not* show any key // information but just states that things are BAD. // // The reason for this: In this case ALL information // might be falsificated, we can NOT trust the data // in the body NOT the signature - so we don't show // any key/signature information at all! // (khz, according to LinuxTag 2002 meeting) showKeyInfos = false; frameColor = SIG_FRAME_COL_RED; } else result = ""; if( SIG_FRAME_COL_GREEN == frameColor ) { result = i18n("Good signature."); } else if( SIG_FRAME_COL_RED == frameColor ) { result = i18n("Bad signature."); } else result = ""; if( !result2.isEmpty() ) { if( !result.isEmpty() ) result.append("
"); result.append( result2 ); } } /* // add i18n support for 3rd party plug-ins here: else if (0 <= cryptPlug->libName().find( "yetanotherpluginname", 0, false )) { } */ } return result; } static QString writeSimpleSigstatHeader( const PartMetaData &block ) { QString html; html += "
"; if ( block.signClass == "signErr" ) { html += i18n( "Invalid signature." ); } else if ( block.signClass == "signOkKeyBad" || block.signClass == "signWarn" ) { html += i18n( "Not enough information to check signature validity." ); } else if ( block.signClass == "signOkKeyOk" ) { QString addr; if ( !block.signerMailAddresses.isEmpty() ) addr = block.signerMailAddresses.first(); QString name = addr; if ( name.isEmpty() ) name = block.signer; if ( addr.isEmpty() ) { html += i18n( "Signature is valid." ); } else { html += i18n( "Signed by %2." ).arg( addr, name ); } } else { // should not happen html += i18n( "Unknown signature state" ); } html += ""; html += ""; html += i18n( "Show Details" ); html += "
"; return html; } static QString beginVerboseSigstatHeader() { return ""; html += "
"; } static QString makeShowAuditLogLink( const GpgME::Error & err, const QString & auditLog ) { if ( const unsigned int code = err.code() ) { if ( code == GPG_ERR_NOT_IMPLEMENTED ) { //kdDebug(5006) << "makeShowAuditLogLink: not showing link (not implemented)" << endl; return QString(); } else if ( code == GPG_ERR_NO_DATA ) { //kdDebug(5006) << "makeShowAuditLogLink: not showing link (not available)" << endl; return i18n("No Audit Log available"); } else { return i18n("Error Retrieving Audit Log: %1").arg( QString::fromLocal8Bit( err.asString() ) ); } } if ( !auditLog.isEmpty() ) { KURL url; url.setProtocol( "kmail" ); url.setPath( "showAuditLog" ); url.addQueryItem( "log", auditLog ); return "" + i18n("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + ""; } return QString::null; } static QString endVerboseSigstatHeader( const PartMetaData & pmd ) { QString html; html += ""; html += ""; html += i18n( "Hide Details" ); html += "
"; html += makeShowAuditLogLink( pmd.auditLogError, pmd.auditLog ); html += "
"; return html; } QString ObjectTreeParser::writeSigstatHeader( PartMetaData & block, const Kleo::CryptoBackend::Protocol * cryptProto, const QString & fromAddress, partNode *node ) { const bool isSMIME = cryptProto && ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() ); QString signer = block.signer; QString htmlStr, simpleHtmlStr; QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" ); QString cellPadding("cellpadding=\"1\""); if( block.isEncapsulatedRfc822Message ) { htmlStr += "" "
"; if ( node ) htmlStr += "asHREF( "body" ) + "\">" + i18n("Encapsulated message") + ""; else htmlStr += i18n("Encapsulated message"); htmlStr += "
"; } if( block.isEncrypted ) { htmlStr += "" "
"; if ( block.inProgress ) htmlStr += i18n("Please wait while the message is being decrypted..."); else if ( block.isDecryptable ) htmlStr += i18n("Encrypted message"); else { htmlStr += i18n("Encrypted message (decryption not possible)"); if( !block.errorText.isEmpty() ) htmlStr += "
" + i18n("Reason: %1").arg( block.errorText ); } htmlStr += "
"; } if ( block.isSigned && block.inProgress ) { block.signClass = "signInProgress"; htmlStr += "" "
"; htmlStr += i18n("Please wait while the signature is being verified..."); htmlStr += "
"; } simpleHtmlStr = htmlStr; if ( block.isSigned && !block.inProgress ) { QStringList& blockAddrs( block.signerMailAddresses ); // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures // pending(khz): Implement usage of these for PGP sigs as well. int frameColor = SIG_FRAME_COL_UNDEF; bool showKeyInfos; bool onlyShowKeyURL = false; bool cannotCheckSignature = true; QString statusStr = sigStatusToString( cryptProto, block.status_code, block.sigSummary, frameColor, showKeyInfos ); // See the comment on the GNUPG_BAD_DIGEST_KEYID for more info // why this is neccessary. if ( block.keyId == GNUPG_BAD_DIGEST_KEYID ) { kdDebug() << "Invalid keyid reported by ." << endl; block.isGoodSignature = false; // In this case the signer may be wrong block.signer = QString(); block.keyId = QCString(); statusStr = i18n( "Message was signed with a key that uses an insecure digest algorithm." ); block.status = statusStr; } // if needed fallback to english status text // that was reported by the plugin if( statusStr.isEmpty() ) statusStr = block.status; if( block.technicalProblem ) frameColor = SIG_FRAME_COL_YELLOW; switch( frameColor ){ case SIG_FRAME_COL_RED: cannotCheckSignature = false; break; case SIG_FRAME_COL_YELLOW: cannotCheckSignature = true; break; case SIG_FRAME_COL_GREEN: cannotCheckSignature = false; break; } // compose the string for displaying the key ID // either as URL or not linked (for PGP) // note: Once we can start PGP key manager programs // from within KMail we could change this and // always show the URL. (khz, 2002/06/27) QString startKeyHREF; if( isSMIME ) startKeyHREF = QString("") .arg( cryptProto->displayName(), cryptProto->name(), block.keyId ); QString keyWithWithoutURL = isSMIME ? QString("%1%2") .arg( startKeyHREF, cannotCheckSignature ? i18n("[Details]") : ("0x" + block.keyId) ) : "0x" + QString::fromUtf8( block.keyId ); // temporary hack: always show key infos! showKeyInfos = true; // Sorry for using 'black' as null color but .isValid() // checking with QColor default c'tor did not work for // some reason. if( isSMIME && (SIG_FRAME_COL_UNDEF != frameColor) ) { // new frame settings for CMS: // beautify the status string if( !statusStr.isEmpty() ) { statusStr.prepend(""); statusStr.append( ""); } // special color handling: S/MIME uses only green/yellow/red. switch( frameColor ) { case SIG_FRAME_COL_RED: block.signClass = "signErr";//"signCMSRed"; onlyShowKeyURL = true; break; case SIG_FRAME_COL_YELLOW: if( block.technicalProblem ) block.signClass = "signWarn"; else block.signClass = "signOkKeyBad";//"signCMSYellow"; break; case SIG_FRAME_COL_GREEN: block.signClass = "signOkKeyOk";//"signCMSGreen"; // extra hint for green case // that email addresses in DN do not match fromAddress QString greenCaseWarning; QString msgFrom( KPIM::getEmailAddress(fromAddress) ); QString certificate; if( block.keyId.isEmpty() ) certificate = i18n("certificate"); else certificate = startKeyHREF + i18n("certificate") + ""; if( !blockAddrs.empty() ){ if( blockAddrs.grep( msgFrom, false ).isEmpty() ) { greenCaseWarning = "" + i18n("Warning:") + " " + i18n("Sender's mail address is not stored " "in the %1 used for signing.").arg(certificate) + "
" + i18n("sender: ") + msgFrom + "
" + i18n("stored: "); // We cannot use Qt's join() function here but // have to join the addresses manually to // extract the mail addresses (without '<''>') // before including it into our string: bool bStart = true; for(QStringList::ConstIterator it = blockAddrs.begin(); it != blockAddrs.end(); ++it ){ if( !bStart ) greenCaseWarning.append(",
   "); bStart = false; greenCaseWarning.append( KPIM::getEmailAddress(*it) ); } } } else { greenCaseWarning = "" + i18n("Warning:") + " " + i18n("No mail address is stored in the %1 used for signing, " "so we cannot compare it to the sender's address %2.") .arg(certificate,msgFrom); } if( !greenCaseWarning.isEmpty() ) { if( !statusStr.isEmpty() ) statusStr.append("
 
"); statusStr.append( greenCaseWarning ); } break; } QString frame = "" "
"; htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader( block ); if( block.technicalProblem ) { htmlStr += block.errorText; } else if( showKeyInfos ) { if( cannotCheckSignature ) { htmlStr += i18n( "Not enough information to check " "signature. %1" ) .arg( keyWithWithoutURL ); } else { if (block.signer.isEmpty()) signer = ""; else { if( !blockAddrs.empty() ){ QString address = KMMessage::encodeMailtoUrl( blockAddrs.first() ); signer = "" + signer + ""; } } if( block.keyId.isEmpty() ) { if( signer.isEmpty() || onlyShowKeyURL ) htmlStr += i18n( "Message was signed with unknown key." ); else htmlStr += i18n( "Message was signed by %1." ) .arg( signer ); } else { QDateTime created = block.creationTime; if( created.isValid() ) { if( signer.isEmpty() ) { if( onlyShowKeyURL ) htmlStr += i18n( "Message was signed with key %1." ) .arg( keyWithWithoutURL ); else htmlStr += i18n( "Message was signed on %1 with key %2." ) .arg( KGlobal::locale()->formatDateTime( created ), keyWithWithoutURL ); } else { if( onlyShowKeyURL ) htmlStr += i18n( "Message was signed with key %1." ) .arg( keyWithWithoutURL ); else htmlStr += i18n( "Message was signed by %3 on %1 with key %2" ) .arg( KGlobal::locale()->formatDateTime( created ), keyWithWithoutURL, signer ); } } else { if( signer.isEmpty() || onlyShowKeyURL ) htmlStr += i18n( "Message was signed with key %1." ) .arg( keyWithWithoutURL ); else htmlStr += i18n( "Message was signed by %2 with key %1." ) .arg( keyWithWithoutURL, signer ); } } } htmlStr += "
"; if( !statusStr.isEmpty() ) { htmlStr += " 
"; htmlStr += i18n( "Status: " ); htmlStr += statusStr; } } else { htmlStr += statusStr; } frame = "
"; htmlStr += endVerboseSigstatHeader( block ) + frame; simpleHtmlStr += frame; } else { // old frame settings for PGP: if( block.signer.isEmpty() || block.technicalProblem ) { block.signClass = "signWarn"; QString frame = "" "
"; htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader( block ); if( block.technicalProblem ) { htmlStr += block.errorText; } else { if( !block.keyId.isEmpty() ) { QDateTime created = block.creationTime; if ( created.isValid() ) htmlStr += i18n( "Message was signed on %1 with unknown key %2." ) .arg( KGlobal::locale()->formatDateTime( created ), keyWithWithoutURL ); else htmlStr += i18n( "Message was signed with unknown key %1." ) .arg( keyWithWithoutURL ); } else htmlStr += i18n( "Message was signed with unknown key." ); htmlStr += "
"; htmlStr += i18n( "The validity of the signature cannot be " "verified." ); if( !statusStr.isEmpty() ) { htmlStr += "
"; htmlStr += i18n( "Status: " ); htmlStr += ""; htmlStr += statusStr; htmlStr += ""; } } frame = "
"; htmlStr += endVerboseSigstatHeader( block ) + frame; simpleHtmlStr += frame; } else { // HTMLize the signer's user id and create mailto: link signer = KMMessage::quoteHtmlChars( signer, true ); signer = "" + signer + ""; if (block.isGoodSignature) { if( block.keyTrust < Kpgp::KPGP_VALIDITY_MARGINAL ) block.signClass = "signOkKeyBad"; else block.signClass = "signOkKeyOk"; QString frame = "" "" "
"; htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader( block ); if( !block.keyId.isEmpty() ) htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." ) .arg( keyWithWithoutURL, signer ); else htmlStr += i18n( "Message was signed by %1." ).arg( signer ); htmlStr += "
"; switch( block.keyTrust ) { case Kpgp::KPGP_VALIDITY_UNKNOWN: htmlStr += i18n( "The signature is valid, but the key's " "validity is unknown." ); break; case Kpgp::KPGP_VALIDITY_MARGINAL: htmlStr += i18n( "The signature is valid and the key is " "marginally trusted." ); break; case Kpgp::KPGP_VALIDITY_FULL: htmlStr += i18n( "The signature is valid and the key is " "fully trusted." ); break; case Kpgp::KPGP_VALIDITY_ULTIMATE: htmlStr += i18n( "The signature is valid and the key is " "ultimately trusted." ); break; default: htmlStr += i18n( "The signature is valid, but the key is " "untrusted." ); } frame = "
"; htmlStr += endVerboseSigstatHeader( block ) + frame; simpleHtmlStr += frame; } else { block.signClass = "signErr"; QString frame = "" "" ""; htmlStr += "
"; htmlStr += frame + beginVerboseSigstatHeader(); simpleHtmlStr += frame; simpleHtmlStr += writeSimpleSigstatHeader( block ); if( !block.keyId.isEmpty() ) htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." ) .arg( keyWithWithoutURL, signer ); else htmlStr += i18n( "Message was signed by %1." ).arg( signer ); htmlStr += "
"; htmlStr += i18n("Warning: The signature is bad."); frame = "
"; htmlStr += endVerboseSigstatHeader( block ) + frame; simpleHtmlStr += frame; } } } } if ( mReader->showSignatureDetails() ) return htmlStr; return simpleHtmlStr; } QString ObjectTreeParser::writeSigstatFooter( PartMetaData& block ) { QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" ); QString htmlStr; if (block.isSigned) { htmlStr += "
" + i18n( "End of signed message" ) + "
"; } if (block.isEncrypted) { htmlStr += "
" + i18n( "End of encrypted message" ) + "
"; } if( block.isEncapsulatedRfc822Message ) { htmlStr += "
" + i18n( "End of encapsulated message" ) + "
"; } return htmlStr; } //----------------------------------------------------------------------------- void ObjectTreeParser::writeAttachmentMarkHeader( partNode *node ) { if ( !mReader ) return; htmlWriter()->queue( QString( "
\n" ).arg( node->nodeId() ) ); } //----------------------------------------------------------------------------- void ObjectTreeParser::writeAttachmentMarkFooter() { if ( !mReader ) return; htmlWriter()->queue( QString( "
" ) ); } //----------------------------------------------------------------------------- void ObjectTreeParser::writeBodyStr( const QCString& aStr, const QTextCodec *aCodec, const QString& fromAddress ) { KMMsgSignatureState dummy1; KMMsgEncryptionState dummy2; writeBodyStr( aStr, aCodec, fromAddress, dummy1, dummy2, false ); } //----------------------------------------------------------------------------- void ObjectTreeParser::writeBodyStr( const QCString& aStr, const QTextCodec *aCodec, const QString& fromAddress, KMMsgSignatureState& inlineSignatureState, KMMsgEncryptionState& inlineEncryptionState, bool decorate ) { QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" ); QString headerStr = QString("
").arg(dir); inlineSignatureState = KMMsgNotSigned; inlineEncryptionState = KMMsgNotEncrypted; QPtrList pgpBlocks; QStrList nonPgpBlocks; if( Kpgp::Module::prepareMessageForDecryption( aStr, pgpBlocks, nonPgpBlocks ) ) { bool isEncrypted = false, isSigned = false; bool fullySignedOrEncrypted = true; bool firstNonPgpBlock = true; bool couldDecrypt = false; QString signer; QCString keyId; QString decryptionError; Kpgp::Validity keyTrust = Kpgp::KPGP_VALIDITY_FULL; QPtrListIterator pbit( pgpBlocks ); QStrListIterator npbit( nonPgpBlocks ); for( ; *pbit != 0; ++pbit, ++npbit ) { // insert the next Non-OpenPGP block QCString str( *npbit ); if( !str.isEmpty() ) { htmlWriter()->queue( quotedHTML( aCodec->toUnicode( str ), decorate ) ); //kdDebug( 5006 ) << "Non-empty Non-OpenPGP block found: '" << str // << "'" << endl; // treat messages with empty lines before the first clearsigned // block as fully signed/encrypted if( firstNonPgpBlock ) { // check whether str only consists of \n for( QCString::ConstIterator c = str.begin(); *c; ++c ) { if( *c != '\n' ) { fullySignedOrEncrypted = false; break; } } } else { fullySignedOrEncrypted = false; } } firstNonPgpBlock = false; //htmlStr += "
"; Kpgp::Block* block = *pbit; // Identify the block gpgme_data_t dh; gpgme_data_new_from_mem (&dh, block->text(), strlen(block->text()), 1); gpgme_data_type_t me_type = gpgme_data_identify (dh, 0); gpgme_data_release (dh); if ( me_type == GPGME_DATA_TYPE_PGP_ENCRYPTED ) { setCryptoProtocol( Kleo::CryptoBackendFactory::instance()->openpgp() ); if ( mReader && !mReader->decryptMessage() ) { writeDeferredDecryptionBlock(); continue; } // Do the decrypt. PartMetaData messagePart; QCString decryptedData; bool signatureFound; std::vector signatures; bool passphraseError; bool actuallyEncrypted = true; bool decryptionStarted; DwBodyPart *newBody = new DwBodyPart( DwString ( block->text() ) ); newBody->Parse(); partNode* newNode = new partNode(true, newBody ); // We are lazy and don't allow async for simplicity. mAllowAsync = false; bool bOkDecrypt = okDecryptMIME( *newNode, decryptedData, signatureFound, signatures, true, passphraseError, actuallyEncrypted, decryptionStarted, messagePart.errorText, messagePart.auditLogError, messagePart.auditLog ); mAllowAsync = true; isEncrypted = actuallyEncrypted; if ( decryptionStarted ) { // Should not happen as we don't allow async. writeDecryptionInProgressBlock(); continue; } // paint the frame if ( mReader ) { messagePart.isDecryptable = bOkDecrypt; messagePart.isEncrypted = true; messagePart.isSigned = false; htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), fromAddress ) ); } if ( bOkDecrypt ) { if ( signatureFound ) { - QCString cleartext = aCodec->toUnicode( decryptedData ).utf8(); writeOpaqueOrMultipartSignedData( 0, *newNode, fromAddress, false, - &cleartext, + &decryptedData, signatures, false, - true ); + true, + aCodec); newNode->setSignatureState( KMMsgFullySigned ); isSigned = true; } else { mRawReplyString += decryptedData; if ( mReader ) { htmlWriter()->queue( quotedHTML( aCodec->toUnicode( decryptedData.data() ), decorate ) ); } } } else { kdDebug () << "Error decrypting msgpart: " << messagePart.errorText << endl; mRawReplyString += decryptedData; if ( mReader ) { // print the error message that was returned in decryptedData // (utf8-encoded) htmlWriter()->queue( decryptedData.isEmpty() ? messagePart.errorText : QString::fromUtf8( decryptedData.data() ) ); } } if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } else if( me_type == GPGME_DATA_TYPE_PGP_SIGNED ) { setCryptoProtocol( Kleo::CryptoBackendFactory::instance()->openpgp() ); // Do the decrypt. PartMetaData messagePart; std::vector signatures; DwBodyPart *newBody = new DwBodyPart( DwString ( block->text() ) ); newBody->Parse(); partNode* newNode = new partNode(true, newBody ); messagePart.isSigned = true; messagePart.isEncrypted = false; // Write the header mAllowAsync = false; writeOpaqueOrMultipartSignedData( 0, *newNode, fromAddress, true, NULL, signatures, false ); } else // block is neither message block nor clearsigned block { htmlWriter()->queue( quotedHTML( aCodec->toUnicode( block->text() ), decorate ) ); } } if( isSigned ) inlineSignatureState = KMMsgPartiallySigned; if( isEncrypted ) inlineEncryptionState = KMMsgPartiallyEncrypted; // add the last Non-OpenPGP block QCString str( nonPgpBlocks.last() ); if( !str.isEmpty() ) { htmlWriter()->queue( quotedHTML( aCodec->toUnicode( str ), decorate ) ); fullySignedOrEncrypted = false; // Even if the trailing Non-OpenPGP block isn't empty we still // consider the message part fully signed/encrypted because else // all inline signed mailing list messages would only be partially // signed because of the footer which is often added by the mailing // list software. IK, 2003-02-15 } if( fullySignedOrEncrypted ) { if( inlineSignatureState == KMMsgPartiallySigned ) inlineSignatureState = KMMsgFullySigned; if( inlineEncryptionState == KMMsgPartiallyEncrypted ) inlineEncryptionState = KMMsgFullyEncrypted; } } else htmlWriter()->queue( quotedHTML( aCodec->toUnicode( aStr ), decorate ) ); } QString ObjectTreeParser::quotedHTML( const QString& s, bool decorate ) { assert( mReader ); assert( cssHelper() ); int convertFlags = LinkLocator::PreserveSpaces; if ( decorate && GlobalSettings::self()->showEmoticons() ) { convertFlags |= LinkLocator::ReplaceSmileys; } QString htmlStr; const QString normalStartTag = cssHelper()->nonQuotedFontTag(); QString quoteFontTag[3]; QString deepQuoteFontTag[3]; for ( int i = 0 ; i < 3 ; ++i ) { quoteFontTag[i] = cssHelper()->quoteFontTag( i ); deepQuoteFontTag[i] = cssHelper()->quoteFontTag( i+3 ); } const QString normalEndTag = "
"; const QString quoteEnd = ""; unsigned int pos, beg; const unsigned int length = s.length(); // skip leading empty lines for ( pos = 0; pos < length && s[pos] <= ' '; pos++ ) { ; } while (pos > 0 && (s[pos-1] == ' ' || s[pos-1] == '\t')) pos--; beg = pos; int currQuoteLevel = -2; // -2 == no previous lines bool curHidden = false; // no hide any block while (begshowExpandQuotesMark() ) { // Cache Icons if ( mCollapseIcon.isEmpty() ) { mCollapseIcon= LinkLocator::pngToDataUrl( KGlobal::instance()->iconLoader()->iconPath( "quotecollapse",0 )); } if ( mExpandIcon.isEmpty() ) mExpandIcon= LinkLocator::pngToDataUrl( KGlobal::instance()->iconLoader()->iconPath( "quoteexpand",0 )); } for (unsigned int p=0; p': case '|': actQuoteLevel++; break; case ' ': // spaces and tabs are allowed between the quote markers case '\t': case '\r': break; default: // stop quoting depth calculation p = line.length(); break; } } /* for() */ bool actHidden = false; QString textExpand; // This quoted line needs be hiden if (GlobalSettings::self()->showExpandQuotesMark() && mReader->mLevelQuote >= 0 && mReader->mLevelQuote <= ( actQuoteLevel ) ) actHidden = true; if ( actQuoteLevel != currQuoteLevel ) { /* finish last quotelevel */ if (currQuoteLevel == -1) htmlStr.append( normalEndTag ); else if ( currQuoteLevel >= 0 && !curHidden ) htmlStr.append( quoteEnd ); /* start new quotelevel */ if (actQuoteLevel == -1) htmlStr += normalStartTag; else { if ( GlobalSettings::self()->showExpandQuotesMark() ) { if ( actHidden ) { //only show the QuoteMark when is the first line of the level hidden if ( !curHidden ) { //Expand all quotes htmlStr += "
" ; htmlStr += QString( "" "\"\"" ) .arg(-1) .arg( mExpandIcon ); htmlStr += "

"; htmlStr += quoteEnd; } }else { htmlStr += "
" ; htmlStr += QString( "" "\"\"" ) .arg(actQuoteLevel) .arg( mCollapseIcon); htmlStr += "
"; if ( actQuoteLevel < 3 ) htmlStr += quoteFontTag[actQuoteLevel]; else htmlStr += deepQuoteFontTag[actQuoteLevel%3]; } } else if ( actQuoteLevel < 3 ) htmlStr += quoteFontTag[actQuoteLevel]; else htmlStr += deepQuoteFontTag[actQuoteLevel%3]; } currQuoteLevel = actQuoteLevel; } curHidden = actHidden; if ( !actHidden ) { // don't write empty
blocks (they have zero height) // ignore ^M DOS linebreaks if( !line.replace('\015', "").isEmpty() ) { htmlStr +=QString( "
" ).arg( line.isRightToLeft() ? "rtl":"ltr" ); htmlStr += LinkLocator::convertToHtml( line, convertFlags ); htmlStr += QString( "
" ); } else htmlStr += "
"; } } /* while() */ /* really finish the last quotelevel */ if (currQuoteLevel == -1) htmlStr.append( normalEndTag ); else htmlStr.append( quoteEnd ); return htmlStr; } const QTextCodec * ObjectTreeParser::codecFor( partNode * node ) const { assert( node ); if ( mReader && mReader->overrideCodec() ) return mReader->overrideCodec(); return node->msgPart().codec(); } #ifdef MARCS_DEBUG void ObjectTreeParser::dumpToFile( const char * filename, const char * start, size_t len ) { assert( filename ); QFile f( filename ); if ( f.open( IO_WriteOnly ) ) { if ( start ) { QDataStream ds( &f ); ds.writeRawBytes( start, len ); } f.close(); // If data is 0 we just create a zero length file. } } #endif // !NDEBUG } // namespace KMail diff --git a/kmail/objecttreeparser.h b/kmail/objecttreeparser.h index 5c942cbf67..8b0f8c90fc 100644 --- a/kmail/objecttreeparser.h +++ b/kmail/objecttreeparser.h @@ -1,364 +1,365 @@ /* -*- mode: C++; c-file-style: "gnu" -*- objecttreeparser.h This file is part of KMail, the KDE mail client. Copyright (c) 2002-2003 Klarälvdalens Datakonsult AB Copyright (c) 2003 Marc Mutz KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef _KMAIL_OBJECTTREEPARSER_H_ #define _KMAIL_OBJECTTREEPARSER_H_ #include "kmmsgbase.h" #include #include #include #include class KMReaderWin; class KMMessagePart; class QString; class QWidget; class partNode; namespace GpgME { class Error; } namespace KMail { class AttachmentStrategy; class HtmlWriter; class PartMetaData; class CSSHelper; class ProcessResult { public: ProcessResult( KMMsgSignatureState inlineSignatureState = KMMsgNotSigned, KMMsgEncryptionState inlineEncryptionState = KMMsgNotEncrypted, bool neverDisplayInline = false, bool isImage = false ) : mInlineSignatureState( inlineSignatureState ), mInlineEncryptionState( inlineEncryptionState ), mNeverDisplayInline( neverDisplayInline ), mIsImage( isImage ) {} KMMsgSignatureState inlineSignatureState() const { return mInlineSignatureState; } void setInlineSignatureState( KMMsgSignatureState state ) { mInlineSignatureState = state; } KMMsgEncryptionState inlineEncryptionState() const { return mInlineEncryptionState; } void setInlineEncryptionState( KMMsgEncryptionState state ) { mInlineEncryptionState = state; } bool neverDisplayInline() const { return mNeverDisplayInline; } void setNeverDisplayInline( bool display ) { mNeverDisplayInline = display; } bool isImage() const { return mIsImage; } void setIsImage( bool image ) { mIsImage = image; } void adjustCryptoStatesOfNode( partNode * node ) const; private: KMMsgSignatureState mInlineSignatureState; KMMsgEncryptionState mInlineEncryptionState; bool mNeverDisplayInline : 1; bool mIsImage : 1; }; class ObjectTreeParser { class CryptoProtocolSaver; /** Internal. Copies the context of @p other, but not it's rawReplyString() */ ObjectTreeParser( const ObjectTreeParser & other ); public: ObjectTreeParser( KMReaderWin * reader=0, const Kleo::CryptoBackend::Protocol * protocol=0, bool showOneMimePart=false, bool keepEncryptions=false, bool includeSignatures=true, const KMail::AttachmentStrategy * attachmentStrategy=0, KMail::HtmlWriter * htmlWriter=0, KMail::CSSHelper * cssHelper=0 ); virtual ~ObjectTreeParser(); void setAllowAsync( bool allow ) { assert( !mHasPendingAsyncJobs ); mAllowAsync = allow; } bool allowAsync() const { return mAllowAsync; } bool hasPendingAsyncJobs() const { return mHasPendingAsyncJobs; } /*! @return true if a multipart/alternative was processed and the plain part * choosen for display. */ bool isHTMLAlternativeAvailable() const { return mIsHTMLAlternativeAvailable; } int rfc822RecursionCnt() const { return mRFC822RecursionCnt; } void setRFC822RecursionCnt (int value ) { mRFC822RecursionCnt = value; } QCString rawReplyString() const { return mRawReplyString; } /*! @return the text of the message, ie. what would appear in the composer's text editor if this was edited. */ QString textualContent() const { return mTextualContent; } QCString textualContentCharset() const { return mTextualContentCharset; } void setCryptoProtocol( const Kleo::CryptoBackend::Protocol * protocol ) { mCryptoProtocol = protocol; } const Kleo::CryptoBackend::Protocol* cryptoProtocol() const { return mCryptoProtocol; } bool showOnlyOneMimePart() const { return mShowOnlyOneMimePart; } void setShowOnlyOneMimePart( bool show ) { mShowOnlyOneMimePart = show; } bool keepEncryptions() const { return mKeepEncryptions; } void setKeepEncryptions( bool keep ) { mKeepEncryptions = keep; } bool includeSignatures() const { return mIncludeSignatures; } void setIncludeSignatures( bool include ) { mIncludeSignatures = include; } // Controls whether Toltec invitations are displayed in their raw form or as a replacement text, // which is used in processToltecMail(). void setShowRawToltecMail( bool showRawToltecMail ) { mShowRawToltecMail = showRawToltecMail; } bool showRawToltecMail() const { return mShowRawToltecMail; } /// default text for processToltecMail(), which is used in kmail.kcfg, therefore it /// needs to be static here. static QString defaultToltecReplacementText(); const KMail::AttachmentStrategy * attachmentStrategy() const { return mAttachmentStrategy; } KMail::HtmlWriter * htmlWriter() const { return mHtmlWriter; } KMail::CSSHelper * cssHelper() const { return mCSSHelper; } /** Parse beginning at a given node and recursively parsing the children of that node and it's next sibling. */ // Function is called internally by "parseMsg(KMMessage* msg)" // and it will be replaced once KMime is alive. void parseObjectTree( partNode * node ); private: /** Standard children handling a.k.a. multipart/mixed (w/o kroupware hacks) */ void stdChildHandling( partNode * child ); void defaultHandling( partNode * node, ProcessResult & result ); /** * 1. Create a new partNode using 'content' data and Content-Description * found in 'cntDesc'. * 2. Make this node the child of 'node'. * 3. Insert the respective entries in the Mime Tree Viewer. * 3. Parse the 'node' to display the content. * * @param addToTextualContent If true, this will add the textual content of the parsed node * to the textual content of the current object tree parser. * Setting this to false is useful for encapsulated messages, as we * do not want the text in those to appear in the editor */ // Function will be replaced once KMime is alive. void insertAndParseNewChildNode( partNode & node, const char * content, const char * cntDesc, bool append=false, bool addToTextualContent = true ); /** if data is 0: Feeds the HTML widget with the contents of the opaque signed data found in partNode 'sign'. if data is set: Feeds the HTML widget with the contents of the given multipart/signed object. Signature is tested. May contain body parts. Returns whether a signature was found or not: use this to find out if opaque data is signed or not. */ bool writeOpaqueOrMultipartSignedData( partNode * data, partNode & sign, const QString & fromAddress, bool doCheck=true, QCString * cleartextData=0, const std::vector & paramSignatures = std::vector(), bool hideErrors=false, - bool isPGPInline=false ); + bool isPGPInline=false, + const QTextCodec *aCodec=NULL); /** Writes out the block that we use when the node is encrypted, but we're deferring decryption for later. */ void writeDeferredDecryptionBlock(); /** Writes out the block that we use when the node is encrypted, but we've just kicked off async decryption. */ void writeDecryptionInProgressBlock(); /** Returns the contents of the given multipart/encrypted object. Data is decypted. May contain body parts. */ bool okDecryptMIME( partNode& data, QCString& decryptedData, bool& signatureFound, std::vector &signatures, bool showWarning, bool& passphraseError, bool& actuallyEncrypted, bool& decryptionStarted, QString& aErrorText, GpgME::Error & auditLogError, QString& auditLog ); bool processMailmanMessage( partNode * node ); /** * This is called for all multipart/mixed nodes. It checks if that belongs to a Toltec mail, * by checking various criteria. * If it is a toltec mail, a special text, instead of the confusing toltec text, will be * displayed. * * @return true if the mail was indeed a toltec mail, in which case the node should not be * processed further */ bool processToltecMail( partNode * node ); /** Checks whether @p str contains external references. To be precise, we only check whether @p str contains 'xxx="http[s]:' where xxx is not href. Obfuscated external references are ignored on purpose. */ static bool containsExternalReferences( const QCString & str ); public:// (during refactoring) bool processTextHtmlSubtype( partNode * node, ProcessResult & result ); bool processTextPlainSubtype( partNode * node, ProcessResult & result ); bool processMultiPartMixedSubtype( partNode * node, ProcessResult & result ); bool processMultiPartAlternativeSubtype( partNode * node, ProcessResult & result ); bool processMultiPartDigestSubtype( partNode * node, ProcessResult & result ); bool processMultiPartParallelSubtype( partNode * node, ProcessResult & result ); bool processMultiPartSignedSubtype( partNode * node, ProcessResult & result ); bool processMultiPartEncryptedSubtype( partNode * node, ProcessResult & result ); bool processMessageRfc822Subtype( partNode * node, ProcessResult & result ); bool processApplicationOctetStreamSubtype( partNode * node, ProcessResult & result ); bool processApplicationPkcs7MimeSubtype( partNode * node, ProcessResult & result ); bool processApplicationChiasmusTextSubtype( partNode * node, ProcessResult & result ); bool processApplicationMsTnefSubtype( partNode *node, ProcessResult &result ); private: bool decryptChiasmus( const QByteArray& data, QByteArray& bodyDecoded, QString& errorText ); void writeBodyString( const QCString & bodyString, const QString & fromAddress, const QTextCodec * codec, ProcessResult & result, bool decorate ); void writePartIcon( KMMessagePart * msgPart, int partNumber, bool inlineImage=false ); QString sigStatusToString( const Kleo::CryptoBackend::Protocol * cryptProto, int status_code, GpgME::Signature::Summary summary, int & frameColor, bool & showKeyInfos ); QString writeSigstatHeader( KMail::PartMetaData & part, const Kleo::CryptoBackend::Protocol * cryptProto, const QString & fromAddress, partNode *node = 0 ); QString writeSigstatFooter( KMail::PartMetaData & part ); // The attachment mark is a div that is placed around the attchment. It is used for drawing // a yellow border around the attachment when scrolling to it. When scrolling to it, the border // color of the div is changed, see KMReaderWin::scrollToAttachment(). void writeAttachmentMarkHeader( partNode *node ); void writeAttachmentMarkFooter(); void writeBodyStr( const QCString & bodyString, const QTextCodec * aCodec, const QString & fromAddress, KMMsgSignatureState & inlineSignatureState, KMMsgEncryptionState & inlineEncryptionState, bool decorate ); public: // KMReaderWin still needs this... void writeBodyStr( const QCString & bodyString, const QTextCodec * aCodec, const QString & fromAddress ); private: /** Change the string to `quoted' html (meaning, that the quoted part of the message get italized */ QString quotedHTML(const QString& pos, bool decorate); const QTextCodec * codecFor( partNode * node ) const; #ifdef MARCS_DEBUG void dumpToFile( const char * filename, const char * dataStart, size_t dataLen ); #else void dumpToFile( const char *, const char *, size_t ) {} #endif private: KMReaderWin * mReader; QCString mRawReplyString; QCString mTextualContentCharset; QString mTextualContent; const Kleo::CryptoBackend::Protocol * mCryptoProtocol; bool mShowOnlyOneMimePart; bool mKeepEncryptions; bool mIncludeSignatures; bool mHasPendingAsyncJobs; bool mAllowAsync; bool mShowRawToltecMail; bool mIsHTMLAlternativeAvailable; int mRFC822RecursionCnt; const KMail::AttachmentStrategy * mAttachmentStrategy; KMail::HtmlWriter * mHtmlWriter; KMail::CSSHelper * mCSSHelper; // DataUrl Icons cache QString mCollapseIcon; QString mExpandIcon; }; } // namespace KMail #endif // _KMAIL_OBJECTTREEPARSER_H_