kmail

objecttreeparser.cpp
1 /*
2  objecttreeparser.cpp
3 
4  This file is part of KMail, the KDE mail client.
5  Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
6  Copyright (c) 2003 Marc Mutz <mutz@kde.org>
7 
8  KMail is free software; you can redistribute it and/or modify it
9  under the terms of the GNU General Public License, version 2, as
10  published by the Free Software Foundation.
11 
12  KMail is distributed in the hope that it will be useful, but
13  WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 
21  In addition, as a special exception, the copyright holders give
22  permission to link the code of this program with any edition of
23  the TQt library by Trolltech AS, Norway (or with modified versions
24  of TQt that use the same license as TQt), and distribute linked
25  combinations including the two. You must obey the GNU General
26  Public License in all respects for all of the code used other than
27  TQt. If you modify this file, you may extend this exception to
28  your version of the file, but you are not obligated to do so. If
29  you do not wish to do so, delete this exception statement from
30  your version.
31 */
32 
33 #include <config.h>
34 
35 // my header file
36 #include "objecttreeparser.h"
37 #include "objecttreeparser_p.h"
38 
39 // other KMail headers
40 #include "kmkernel.h"
41 #include "kmreaderwin.h"
42 #include "partNode.h"
43 #include <libtdepim/tdefileio.h>
44 #include <libemailfunctions/email.h>
45 #include "partmetadata.h"
46 #include "attachmentstrategy.h"
47 #include "interfaces/htmlwriter.h"
48 #include "htmlstatusbar.h"
49 #include "csshelper.h"
50 #include "bodypartformatter.h"
51 #include "bodypartformatterfactory.h"
52 #include "partnodebodypart.h"
53 #include "interfaces/bodypartformatter.h"
54 #include "globalsettings.h"
55 #include "util.h"
56 #include "callback.h"
57 
58 // other module headers
59 #include <mimelib/enum.h>
60 #include <mimelib/bodypart.h>
61 #include <mimelib/string.h>
62 #include <mimelib/text.h>
63 
64 #include <kleo/specialjob.h>
65 #include <kleo/cryptobackendfactory.h>
66 #include <kleo/decryptverifyjob.h>
67 #include <kleo/verifydetachedjob.h>
68 #include <kleo/verifyopaquejob.h>
69 #include <kleo/keylistjob.h>
70 #include <kleo/importjob.h>
71 #include <kleo/dn.h>
72 
73 #include <gpgmepp/importresult.h>
74 #include <gpgmepp/decryptionresult.h>
75 #include <gpgmepp/key.h>
76 #include <gpgmepp/keylistresult.h>
77 #include <gpgme.h>
78 
79 #include <kpgpblock.h>
80 #include <kpgp.h>
81 #include <linklocator.h>
82 
83 #include <ktnef/ktnefparser.h>
84 #include <ktnef/ktnefmessage.h>
85 #include <ktnef/ktnefattach.h>
86 
87 // other KDE headers
88 #include <kdebug.h>
89 #include <tdelocale.h>
90 #include <kmimetype.h>
91 #include <tdeglobal.h>
92 #include <tdehtml_part.h>
93 #include <tdetempfile.h>
94 #include <kstandarddirs.h>
95 #include <tdeapplication.h>
96 #include <tdemessagebox.h>
97 #include <kiconloader.h>
98 #include <kmdcodec.h>
99 
100 // other TQt headers
101 #include <tqtextcodec.h>
102 #include <tqdir.h>
103 #include <tqfile.h>
104 #include <tqapplication.h>
105 #include <tdestyle.h>
106 #include <tqbuffer.h>
107 #include <tqpixmap.h>
108 #include <tqpainter.h>
109 #include <tqregexp.h>
110 
111 // other headers
112 #include <memory>
113 #include <sstream>
114 #include <sys/stat.h>
115 #include <sys/types.h>
116 #include <unistd.h>
117 #include <cassert>
118 #include "chiasmuskeyselector.h"
119 
120 namespace KMail {
121 
122  // A small class that eases temporary CryptPlugWrapper changes:
123  class ObjectTreeParser::CryptoProtocolSaver {
124  ObjectTreeParser * otp;
125  const Kleo::CryptoBackend::Protocol * protocol;
126  public:
127  CryptoProtocolSaver( ObjectTreeParser * _otp, const Kleo::CryptoBackend::Protocol* _w )
128  : otp( _otp ), protocol( _otp ? _otp->cryptoProtocol() : 0 )
129  {
130  if ( otp )
131  otp->setCryptoProtocol( _w );
132  }
133 
134  ~CryptoProtocolSaver() {
135  if ( otp )
136  otp->setCryptoProtocol( protocol );
137  }
138  };
139 
140 
141  ObjectTreeParser::ObjectTreeParser( KMReaderWin * reader, const Kleo::CryptoBackend::Protocol * protocol,
142  bool showOnlyOneMimePart, bool keepEncryptions,
143  bool includeSignatures,
144  const AttachmentStrategy * strategy,
145  HtmlWriter * htmlWriter,
146  CSSHelper * cssHelper )
147  : mReader( reader ),
148  mCryptoProtocol( protocol ),
149  mShowOnlyOneMimePart( showOnlyOneMimePart ),
150  mKeepEncryptions( keepEncryptions ),
151  mIncludeSignatures( includeSignatures ),
152  mHasPendingAsyncJobs( false ),
153  mAllowAsync( false ),
154  mShowRawToltecMail( false ),
155  mAttachmentStrategy( strategy ),
156  mHtmlWriter( htmlWriter ),
157  mCSSHelper( cssHelper )
158  {
159  if ( !attachmentStrategy() )
160  mAttachmentStrategy = reader ? reader->attachmentStrategy()
161  : AttachmentStrategy::smart();
162  if ( reader && !this->htmlWriter() )
163  mHtmlWriter = reader->htmlWriter();
164  if ( reader && !this->cssHelper() )
165  mCSSHelper = reader->mCSSHelper;
166  }
167 
168  ObjectTreeParser::ObjectTreeParser( const ObjectTreeParser & other )
169  : mReader( other.mReader ),
170  mCryptoProtocol( other.cryptoProtocol() ),
171  mShowOnlyOneMimePart( other.showOnlyOneMimePart() ),
172  mKeepEncryptions( other.keepEncryptions() ),
173  mIncludeSignatures( other.includeSignatures() ),
174  mHasPendingAsyncJobs( other.hasPendingAsyncJobs() ),
175  mAllowAsync( other.allowAsync() ),
176  mAttachmentStrategy( other.attachmentStrategy() ),
177  mHtmlWriter( other.htmlWriter() ),
178  mCSSHelper( other.cssHelper() )
179  {
180 
181  }
182 
183  ObjectTreeParser::~ObjectTreeParser() {}
184 
185  void ObjectTreeParser::insertAndParseNewChildNode( partNode& startNode,
186  const char* content,
187  const char* cntDesc,
188  bool append, bool addToTextualContent )
189  {
190  DwBodyPart* myBody = new DwBodyPart( DwString( content ), 0 );
191  myBody->Parse();
192 
193  if ( ( !myBody->Body().FirstBodyPart() ||
194  myBody->Body().AsString().length() == 0 ) &&
195  startNode.dwPart() &&
196  startNode.dwPart()->Body().Message() &&
197  startNode.dwPart()->Body().Message()->Body().FirstBodyPart() )
198  {
199  // if encapsulated imap messages are loaded the content-string is not complete
200  // so we need to keep the child dwparts
201  myBody = new DwBodyPart( *(startNode.dwPart()->Body().Message()) );
202  }
203 
204  if ( myBody->hasHeaders() ) {
205  DwText& desc = myBody->Headers().ContentDescription();
206  desc.FromString( cntDesc );
207  desc.SetModified();
208  myBody->Headers().Parse();
209  }
210 
211  partNode* parentNode = &startNode;
212  partNode* newNode = new partNode(false, myBody);
213 
214  // Build the object tree of the new node before setting the parent, as otherwise
215  // buildObjectTree() would erronously modify the parents as well
216  newNode->buildObjectTree( false );
217 
218  if ( append && parentNode->firstChild() ) {
219  parentNode = parentNode->firstChild();
220  while( parentNode->nextSibling() )
221  parentNode = parentNode->nextSibling();
222  parentNode->setNext( newNode );
223  } else
224  parentNode->setFirstChild( newNode );
225 
226  if ( startNode.mimePartTreeItem() ) {
227  newNode->fillMimePartTree( startNode.mimePartTreeItem(), 0,
228  TQString(), TQString(), TQString(), 0,
229  append );
230  } else {
231  }
232  ObjectTreeParser otp( mReader, cryptoProtocol() );
233  otp.parseObjectTree( newNode );
234  if ( addToTextualContent ) {
235  mRawReplyString += otp.rawReplyString();
236  mTextualContent += otp.textualContent();
237  if ( !otp.textualContentCharset().isEmpty() )
238  mTextualContentCharset = otp.textualContentCharset();
239  }
240  }
241 
242 
243 //-----------------------------------------------------------------------------
244 
245  void ObjectTreeParser::parseObjectTree( partNode * node ) {
246  //kdDebug(5006) << "ObjectTreeParser::parseObjectTree( "
247  // << (node ? "node OK, " : "no node, ")
248  // << "showOnlyOneMimePart: " << (showOnlyOneMimePart() ? "TRUE" : "FALSE")
249  // << " )" << endl;
250 
251  if ( !node )
252  return;
253 
254  // reset pending async jobs state (we'll rediscover pending jobs as we go)
255  mHasPendingAsyncJobs = false;
256 
257  // reset "processed" flags for...
258  if ( showOnlyOneMimePart() ) {
259  // ... this node and all descendants
260  node->setProcessed( false, false );
261  if ( partNode * child = node->firstChild() )
262  child->setProcessed( false, true );
263  } else if ( mReader && !node->parentNode() ) {
264  // ...this node and all it's siblings and descendants
265  node->setProcessed( false, true );
266  }
267 
268  for ( ; node ; node = node->nextSibling() ) {
269  if ( node->processed() )
270  continue;
271 
272  ProcessResult processResult;
273 
274  if ( mReader ) {
275  htmlWriter()->queue( TQString::fromLatin1("<a name=\"att%1\"/>").arg( node->nodeId() ) );
276  }
277 
278  if ( const Interface::BodyPartFormatter * formatter
279  = BodyPartFormatterFactory::instance()->createFor( node->typeString(), node->subTypeString() ) ) {
280 
281  // Only use the external plugin if we have a reader. Otherwise, just do nothing for this
282  // node.
283  if ( mReader ) {
284  PartNodeBodyPart part( *node, codecFor( node ) );
285  // Set the default display strategy for this body part relying on the
286  // identity of KMail::Interface::BodyPart::Display and AttachmentStrategy::Display
287  part.setDefaultDisplay( (KMail::Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay( node ) );
288 
289  writeAttachmentMarkHeader( node );
290  node->setDisplayedEmbedded( true );
291  Callback callback( mReader->message(), mReader );
292  const Interface::BodyPartFormatter::Result result = formatter->format( &part, htmlWriter(), callback );
293  writeAttachmentMarkFooter();
294  switch ( result ) {
295  case Interface::BodyPartFormatter::AsIcon:
296  processResult.setNeverDisplayInline( true );
297  // fall through:
298  case Interface::BodyPartFormatter::Failed:
299  defaultHandling( node, processResult );
300  break;
301  case Interface::BodyPartFormatter::Ok:
302  case Interface::BodyPartFormatter::NeedContent:
303  // FIXME: incomplete content handling
304  ;
305  }
306  }
307  } else {
308  const BodyPartFormatter * bpf
309  = BodyPartFormatter::createFor( node->type(), node->subType() );
310  kdFatal( !bpf, 5006 ) << "THIS SHOULD NO LONGER HAPPEN ("
311  << node->typeString() << '/' << node->subTypeString()
312  << ')' << endl;
313 
314  writeAttachmentMarkHeader( node );
315  if ( bpf && !bpf->process( this, node, processResult ) ) {
316  defaultHandling( node, processResult );
317  }
318  writeAttachmentMarkFooter();
319  }
320 
321  node->setProcessed( true, false );
322 
323  // adjust signed/encrypted flags if inline PGP was found
324  processResult.adjustCryptoStatesOfNode( node );
325 
326  if ( showOnlyOneMimePart() )
327  break;
328  }
329  }
330 
331  void ObjectTreeParser::defaultHandling( partNode * node, ProcessResult & result ) {
332  // ### (mmutz) default handling should go into the respective
333  // ### bodypartformatters.
334  if ( !mReader )
335  return;
336 
337 
338  const AttachmentStrategy * as = attachmentStrategy();
339  if ( as && as->defaultDisplay( node ) == AttachmentStrategy::None &&
340  !showOnlyOneMimePart() &&
341  node->parentNode() /* message is not an attachment */ ) {
342  node->setDisplayedHidden( true );
343  return;
344  }
345 
346  bool asIcon = true;
347  if ( showOnlyOneMimePart() )
348  // ### (mmutz) this is wrong! If I click on an image part, I
349  // want the equivalent of "view...", except for the extra
350  // window!
351  asIcon = !node->hasContentDispositionInline();
352  else if ( !result.neverDisplayInline() )
353  if ( as )
354  asIcon = as->defaultDisplay( node ) == AttachmentStrategy::AsIcon;
355  // neither image nor text -> show as icon
356  if ( !result.isImage() && node->type() != DwMime::kTypeText )
357  asIcon = true;
358  // if the image is not complete do not try to show it inline
359  if ( result.isImage() && !node->msgPart().isComplete() )
360  asIcon = true;
361  if ( asIcon ) {
362  if ( !( as && as->defaultDisplay( node ) == AttachmentStrategy::None ) ||
363  showOnlyOneMimePart() ) {
364  writePartIcon( &node->msgPart(), node->nodeId() );
365  }
366  else {
367  node->setDisplayedHidden( true );
368  }
369  } else if ( result.isImage() ) {
370  node->setDisplayedEmbedded( true );
371  writePartIcon( &node->msgPart(), node->nodeId(), true );
372  }
373  else {
374  node->setDisplayedEmbedded( true );
375  writeBodyString( node->msgPart().bodyDecoded(),
376  node->trueFromAddress(),
377  codecFor( node ), result, false );
378  }
379  // end of ###
380  }
381 
382  void ProcessResult::adjustCryptoStatesOfNode( partNode * node ) const {
383  if ( ( inlineSignatureState() != KMMsgNotSigned ) ||
384  ( inlineEncryptionState() != KMMsgNotEncrypted ) ) {
385  node->setSignatureState( inlineSignatureState() );
386  node->setEncryptionState( inlineEncryptionState() );
387  }
388  }
389 
393 
394  static int signatureToStatus( const GpgME::Signature &sig )
395  {
396  switch ( sig.status().code() ) {
397  case GPG_ERR_NO_ERROR:
398  return GPGME_SIG_STAT_GOOD;
399  case GPG_ERR_BAD_SIGNATURE:
400  return GPGME_SIG_STAT_BAD;
401  case GPG_ERR_NO_PUBKEY:
402  return GPGME_SIG_STAT_NOKEY;
403  case GPG_ERR_NO_DATA:
404  return GPGME_SIG_STAT_NOSIG;
405  case GPG_ERR_SIG_EXPIRED:
406  return GPGME_SIG_STAT_GOOD_EXP;
407  case GPG_ERR_KEY_EXPIRED:
408  return GPGME_SIG_STAT_GOOD_EXPKEY;
409  default:
410  return GPGME_SIG_STAT_ERROR;
411  }
412  }
413 
414  bool ObjectTreeParser::writeOpaqueOrMultipartSignedData( partNode* data,
415  partNode& sign,
416  const TQString& fromAddress,
417  bool doCheck,
418  TQCString* cleartextData,
419  const std::vector<GpgME::Signature> & paramSignatures,
420  bool hideErrors )
421  {
422  bool bIsOpaqueSigned = false;
423  enum { NO_PLUGIN, NOT_INITIALIZED, CANT_VERIFY_SIGNATURES }
424  cryptPlugError = NO_PLUGIN;
425 
426  const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
427 
428  TQString cryptPlugLibName;
429  TQString cryptPlugDisplayName;
430  if ( cryptProto ) {
431  cryptPlugLibName = cryptProto->name();
432  cryptPlugDisplayName = cryptProto->displayName();
433  }
434 
435 #ifndef NDEBUG
436  if ( !doCheck ) {
437  //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: showing OpenPGP (Encrypted+Signed) data" << endl;
438  }
439  else {
440  if ( data ) {
441  //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Multipart Signed data" << endl;
442  }
443  else {
444  //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Opaque Signed data" << endl;
445  }
446  }
447 #endif
448 
449  if ( doCheck && cryptProto ) {
450  //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: going to call CRYPTPLUG "
451  // << cryptPlugLibName << endl;
452  }
453 
454  TQCString cleartext;
455  TQByteArray signaturetext;
456 
457  if ( doCheck && cryptProto ) {
458  if ( data ) {
459  cleartext = KMail::Util::CString( data->dwPart()->AsString() );
460 
461  dumpToFile( "dat_01_reader_signedtext_before_canonicalization",
462  cleartext.data(), cleartext.length() );
463 
464  // replace simple LFs by CRLSs
465  // according to RfC 2633, 3.1.1 Canonicalization
466  //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
467  cleartext = Util::lf2crlf( cleartext );
468  //kdDebug(5006) << " done." << endl;
469  }
470 
471  dumpToFile( "dat_02_reader_signedtext_after_canonicalization",
472  cleartext.data(), cleartext.length() );
473 
474  signaturetext = sign.msgPart().bodyDecodedBinary();
475  dumpToFile( "dat_03_reader.sig", signaturetext.data(),
476  signaturetext.size() );
477  }
478 
479  std::vector<GpgME::Signature> signatures;
480  if ( !doCheck )
481  signatures = paramSignatures;
482 
483  PartMetaData messagePart;
484  messagePart.isSigned = true;
485  messagePart.technicalProblem = ( cryptProto == 0 );
486  messagePart.isGoodSignature = false;
487  messagePart.isEncrypted = false;
488  messagePart.isDecryptable = false;
489  messagePart.keyTrust = Kpgp::KPGP_VALIDITY_UNKNOWN;
490  messagePart.status = i18n("Wrong Crypto Plug-In.");
491  messagePart.status_code = GPGME_SIG_STAT_NONE;
492 
493  GpgME::Key key;
494 
495  if ( doCheck && cryptProto ) {
496  GpgME::VerificationResult result;
497  if ( data ) { // detached
498  const VerifyDetachedBodyPartMemento * m
499  = dynamic_cast<VerifyDetachedBodyPartMemento*>( sign.bodyPartMemento( "verifydetached" ) );
500  if ( !m ) {
501  Kleo::VerifyDetachedJob * job = cryptProto->verifyDetachedJob();
502  if ( !job ) {
503  cryptPlugError = CANT_VERIFY_SIGNATURES;
504  // PENDING(marc) cryptProto = 0 here?
505  } else {
506  TQByteArray plainData = cleartext;
507  plainData.resize( cleartext.size() - 1 );
508  VerifyDetachedBodyPartMemento * newM
509  = new VerifyDetachedBodyPartMemento( job, cryptProto->keyListJob(), signaturetext, plainData );
510  if ( allowAsync() ) {
511  if ( newM->start() ) {
512  messagePart.inProgress = true;
513  mHasPendingAsyncJobs = true;
514  } else {
515  m = newM;
516  }
517  } else {
518  newM->exec();
519  m = newM;
520  }
521  sign.setBodyPartMemento( "verifydetached", newM );
522  }
523  } else if ( m->isRunning() ) {
524  messagePart.inProgress = true;
525  mHasPendingAsyncJobs = true;
526  m = 0;
527  }
528 
529  if ( m ) {
530  result = m->verifyResult();
531  messagePart.auditLogError = m->auditLogError();
532  messagePart.auditLog = m->auditLogAsHtml();
533  key = m->signingKey();
534  }
535  } else { // opaque
536  const VerifyOpaqueBodyPartMemento * m
537  = dynamic_cast<VerifyOpaqueBodyPartMemento*>( sign.bodyPartMemento( "verifyopaque" ) );
538  if ( !m ) {
539  Kleo::VerifyOpaqueJob * job = cryptProto->verifyOpaqueJob();
540  if ( !job ) {
541  cryptPlugError = CANT_VERIFY_SIGNATURES;
542  // PENDING(marc) cryptProto = 0 here?
543  } else {
544  VerifyOpaqueBodyPartMemento * newM
545  = new VerifyOpaqueBodyPartMemento( job, cryptProto->keyListJob(), signaturetext );
546  if ( allowAsync() ) {
547  if ( newM->start() ) {
548  messagePart.inProgress = true;
549  mHasPendingAsyncJobs = true;
550  } else {
551  m = newM;
552  }
553  } else {
554  newM->exec();
555  m = newM;
556  }
557  sign.setBodyPartMemento( "verifyopaque", newM );
558  }
559  } else if ( m->isRunning() ) {
560  messagePart.inProgress = true;
561  mHasPendingAsyncJobs = true;
562  m = 0;
563  }
564 
565  if ( m ) {
566  result = m->verifyResult();
567  const TQByteArray & plainData = m->plainText();
568  cleartext = TQCString( plainData.data(), plainData.size() + 1 );
569  messagePart.auditLogError = m->auditLogError();
570  messagePart.auditLog = m->auditLogAsHtml();
571  key = m->signingKey();
572  }
573  }
574  std::stringstream ss;
575  ss << result;
576  //kdDebug(5006) << ss.str().c_str() << endl;
577  signatures = result.signatures();
578  }
579 
580  if ( doCheck ) {
581  //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: returned from CRYPTPLUG" << endl;
582  }
583 
584  // ### only one signature supported
585  if ( signatures.size() > 0 ) {
586  //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: found signature" << endl;
587  GpgME::Signature signature = signatures[0];
588 
589  messagePart.status_code = signatureToStatus( signature );
590  messagePart.status = TQString::fromUtf8( signature.status().asString() );
591  for ( uint i = 1; i < signatures.size(); ++i ) {
592  if ( signatureToStatus( signatures[i] ) != messagePart.status_code ) {
593  messagePart.status_code = GPGME_SIG_STAT_DIFF;
594  messagePart.status = i18n("Different results for signatures");
595  }
596  }
597  if ( messagePart.status_code & GPGME_SIG_STAT_GOOD )
598  messagePart.isGoodSignature = true;
599 
600  // save extended signature status flags
601  messagePart.sigSummary = signature.summary();
602 
603  if ( key.keyID() )
604  messagePart.keyId = key.keyID();
605  if ( messagePart.keyId.isEmpty() )
606  messagePart.keyId = signature.fingerprint();
607  // ### Ugh. We depend on two enums being in sync:
608  messagePart.keyTrust = (Kpgp::Validity)signature.validity();
609  if ( key.numUserIDs() > 0 && key.userID( 0 ).id() )
610  messagePart.signer = Kleo::DN( key.userID( 0 ).id() ).prettyDN();
611  for ( uint iMail = 0; iMail < key.numUserIDs(); ++iMail ) {
612  // The following if /should/ always result in TRUE but we
613  // won't trust implicitely the plugin that gave us these data.
614  if ( key.userID( iMail ).email() ) {
615  TQString email = TQString::fromUtf8( key.userID( iMail ).email() );
616  // ### work around gpgme 0.3.x / cryptplug bug where the
617  // ### email addresses are specified as angle-addr, not addr-spec:
618  if ( email.startsWith( "<" ) && email.endsWith( ">" ) )
619  email = email.mid( 1, email.length() - 2 );
620  if ( !email.isEmpty() )
621  messagePart.signerMailAddresses.append( email );
622  }
623  }
624 
625  if ( signature.creationTime() )
626  messagePart.creationTime.setTime_t( signature.creationTime() );
627  else
628  messagePart.creationTime = TQDateTime();
629  if ( messagePart.signer.isEmpty() ) {
630  if ( key.numUserIDs() > 0 && key.userID( 0 ).name() )
631  messagePart.signer = Kleo::DN( key.userID( 0 ).name() ).prettyDN();
632  if ( !messagePart.signerMailAddresses.empty() ) {
633  if ( messagePart.signer.isEmpty() )
634  messagePart.signer = messagePart.signerMailAddresses.front();
635  else
636  messagePart.signer += " <" + messagePart.signerMailAddresses.front() + '>';
637  }
638  }
639 
640  //kdDebug(5006) << "\n key id: " << messagePart.keyId
641  // << "\n key trust: " << messagePart.keyTrust
642  // << "\n signer: " << messagePart.signer << endl;
643 
644  } else {
645  messagePart.creationTime = TQDateTime();
646  }
647 
648  if ( !doCheck || !data ){
649  if ( cleartextData || !cleartext.isEmpty() ) {
650  if ( mReader )
651  htmlWriter()->queue( writeSigstatHeader( messagePart,
652  cryptProto,
653  fromAddress ) );
654  bIsOpaqueSigned = true;
655 
656  CryptoProtocolSaver cpws( this, cryptProto );
657  insertAndParseNewChildNode( sign, doCheck ? cleartext.data() : cleartextData->data(),
658  "opaqued signed data" );
659 
660  if ( mReader )
661  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
662 
663  }
664  else if ( !hideErrors ) {
665  TQString txt;
666  txt = "<hr><b><h2>";
667  txt.append( i18n( "The crypto engine returned no cleartext data." ) );
668  txt.append( "</h2></b>" );
669  txt.append( "<br>&nbsp;<br>" );
670  txt.append( i18n( "Status: " ) );
671  if ( !messagePart.status.isEmpty() ) {
672  txt.append( "<i>" );
673  txt.append( messagePart.status );
674  txt.append( "</i>" );
675  }
676  else
677  txt.append( i18n("(unknown)") );
678  if ( mReader )
679  htmlWriter()->queue(txt);
680  }
681  }
682  else {
683  if ( mReader ) {
684  if ( !cryptProto ) {
685  TQString errorMsg;
686  switch ( cryptPlugError ) {
687  case NOT_INITIALIZED:
688  errorMsg = i18n( "Crypto plug-in \"%1\" is not initialized." )
689  .arg( cryptPlugLibName );
690  break;
691  case CANT_VERIFY_SIGNATURES:
692  errorMsg = i18n( "Crypto plug-in \"%1\" cannot verify signatures." )
693  .arg( cryptPlugLibName );
694  break;
695  case NO_PLUGIN:
696  if ( cryptPlugDisplayName.isEmpty() )
697  errorMsg = i18n( "No appropriate crypto plug-in was found." );
698  else
699  errorMsg = i18n( "%1 is either 'OpenPGP' or 'S/MIME'",
700  "No %1 plug-in was found." )
701  .arg( cryptPlugDisplayName );
702  break;
703  }
704  messagePart.errorText = i18n( "The message is signed, but the "
705  "validity of the signature cannot be "
706  "verified.<br />"
707  "Reason: %1" )
708  .arg( errorMsg );
709  }
710 
711  if ( mReader )
712  htmlWriter()->queue( writeSigstatHeader( messagePart,
713  cryptProto,
714  fromAddress ) );
715  }
716 
717  ObjectTreeParser otp( mReader, cryptProto, true );
718  otp.parseObjectTree( data );
719  mRawReplyString += otp.rawReplyString();
720  mTextualContent += otp.textualContent();
721  if ( !otp.textualContentCharset().isEmpty() )
722  mTextualContentCharset = otp.textualContentCharset();
723 
724  if ( mReader )
725  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
726  }
727 
728  //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: done, returning "
729  // << ( bIsOpaqueSigned ? "TRUE" : "FALSE" ) << endl;
730  return bIsOpaqueSigned;
731  }
732 
733 void ObjectTreeParser::writeDecryptionInProgressBlock() {
734  assert( mReader );
735  // PENDING(marc) find an animated icon here:
736  //const TQString iconName = TDEGlobal::instance()->iconLoader()->iconPath( "decrypted", TDEIcon::Small );
737  const TQString decryptedData = i18n("Encrypted data not shown");
738  PartMetaData messagePart;
739  messagePart.isDecryptable = true;
740  messagePart.isEncrypted = true;
741  messagePart.isSigned = false;
742  messagePart.inProgress = true;
743  htmlWriter()->queue( writeSigstatHeader( messagePart,
744  cryptoProtocol(),
745  TQString() ) );
746  //htmlWriter()->queue( decryptedData );
747  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
748 }
749 
750 void ObjectTreeParser::writeDeferredDecryptionBlock() {
751  assert( mReader );
752  const TQString iconName = TDEGlobal::instance()->iconLoader()->iconPath( "decrypted", TDEIcon::Small );
753  const TQString decryptedData =
754  "<div style=\"font-size:large; text-align:center;padding-top:20pt;\">" +
755  i18n("This message is encrypted.") +
756  "</div>"
757  "<div style=\"text-align:center; padding-bottom:20pt;\">"
758  "<a href=\"kmail:decryptMessage\">"
759  "<img src=\"" + iconName + "\"/>" +
760  i18n("Decrypt Message") +
761  "</a></div>";
762  PartMetaData messagePart;
763  messagePart.isDecryptable = true;
764  messagePart.isEncrypted = true;
765  messagePart.isSigned = false;
766  mRawReplyString += decryptedData.utf8();
767  htmlWriter()->queue( writeSigstatHeader( messagePart,
768  cryptoProtocol(),
769  TQString() ) );
770  htmlWriter()->queue( decryptedData );
771  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
772 }
773 
774 bool ObjectTreeParser::okDecryptMIME( partNode& data,
775  TQCString& decryptedData,
776  bool& signatureFound,
777  std::vector<GpgME::Signature> &signatures,
778  bool showWarning,
779  bool& passphraseError,
780  bool& actuallyEncrypted,
781  bool& decryptionStarted,
782  TQString& aErrorText,
783  GpgME::Error & auditLogError,
784  TQString& auditLog )
785 {
786  passphraseError = false;
787  decryptionStarted = false;
788  aErrorText = TQString();
789  auditLogError = GpgME::Error();
790  auditLog = TQString();
791  bool bDecryptionOk = false;
792  enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT }
793  cryptPlugError = NO_PLUGIN;
794 
795  const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
796 
797  TQString cryptPlugLibName;
798  if ( cryptProto )
799  cryptPlugLibName = cryptProto->name();
800 
801  assert( !mReader || mReader->decryptMessage() );
802 
803  if ( cryptProto && !kmkernel->contextMenuShown() ) {
804  TQByteArray ciphertext( data.msgPart().bodyDecodedBinary() );
805 #ifdef MARCS_DEBUG
806  TQCString cipherStr( ciphertext.data(), ciphertext.size() + 1 );
807  bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) &&
808  (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) &&
809  (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) );
810 
811  dumpToFile( "dat_04_reader.encrypted", ciphertext.data(), ciphertext.size() );
812 
813  TQCString deb;
814  deb = "\n\nE N C R Y P T E D D A T A = ";
815  if ( cipherIsBinary )
816  deb += "[binary data]";
817  else {
818  deb += "\"";
819  deb += cipherStr;
820  deb += "\"";
821  }
822  deb += "\n\n";
823  kdDebug(5006) << deb << endl;
824 #endif
825 
826 
827  //kdDebug(5006) << "ObjectTreeParser::decryptMIME: going to call CRYPTPLUG "
828  // << cryptPlugLibName << endl;
829  if ( mReader )
830  emit mReader->noDrag(); // in case pineentry pops up, don't let kmheaders start a drag afterwards
831 
832  // Check whether the memento contains a result from last time:
833  const DecryptVerifyBodyPartMemento * m
834  = dynamic_cast<DecryptVerifyBodyPartMemento*>( data.bodyPartMemento( "decryptverify" ) );
835  if ( !m ) {
836  Kleo::DecryptVerifyJob * job = cryptProto->decryptVerifyJob();
837  if ( !job ) {
838  cryptPlugError = CANT_DECRYPT;
839  cryptProto = 0;
840  } else {
841  DecryptVerifyBodyPartMemento * newM
842  = new DecryptVerifyBodyPartMemento( job, ciphertext );
843  if ( allowAsync() ) {
844  if ( newM->start() ) {
845  decryptionStarted = true;
846  mHasPendingAsyncJobs = true;
847  } else {
848  m = newM;
849  }
850  } else {
851  newM->exec();
852  m = newM;
853  }
854  data.setBodyPartMemento( "decryptverify", newM );
855  }
856  } else if ( m->isRunning() ) {
857  decryptionStarted = true;
858  mHasPendingAsyncJobs = true;
859  m = 0;
860  }
861 
862  if ( m ) {
863  const TQByteArray & plainText = m->plainText();
864  const GpgME::DecryptionResult & decryptResult = m->decryptResult();
865  const GpgME::VerificationResult & verifyResult = m->verifyResult();
866  std::stringstream ss;
867  ss << decryptResult << '\n' << verifyResult;
868  //kdDebug(5006) << ss.str().c_str() << endl;
869  signatureFound = verifyResult.signatures().size() > 0;
870  signatures = verifyResult.signatures();
871  bDecryptionOk = !decryptResult.error();
872  passphraseError = decryptResult.error().isCanceled()
873  || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
874  actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA;
875  aErrorText = TQString::fromLocal8Bit( decryptResult.error().asString() );
876  auditLogError = m->auditLogError();
877  auditLog = m->auditLogAsHtml();
878 
879  //kdDebug(5006) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG"
880  // << endl;
881  if ( bDecryptionOk )
882  decryptedData = TQCString( plainText.data(), plainText.size() + 1 );
883  else if ( mReader && showWarning ) {
884  decryptedData = "<div style=\"font-size:x-large; text-align:center;"
885  "padding:20pt;\">"
886  + i18n("Encrypted data not shown.").utf8()
887  + "</div>";
888  if ( !passphraseError )
889  aErrorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.")
890  .arg( cryptPlugLibName )
891  + "<br />"
892  + i18n("Error: %1").arg( aErrorText );
893  }
894  }
895  }
896 
897  if ( !cryptProto ) {
898  decryptedData = "<div style=\"text-align:center; padding:20pt;\">"
899  + i18n("Encrypted data not shown.").utf8()
900  + "</div>";
901  switch ( cryptPlugError ) {
902  case NOT_INITIALIZED:
903  aErrorText = i18n( "Crypto plug-in \"%1\" is not initialized." )
904  .arg( cryptPlugLibName );
905  break;
906  case CANT_DECRYPT:
907  aErrorText = i18n( "Crypto plug-in \"%1\" cannot decrypt messages." )
908  .arg( cryptPlugLibName );
909  break;
910  case NO_PLUGIN:
911  aErrorText = i18n( "No appropriate crypto plug-in was found." );
912  break;
913  }
914  } else if ( kmkernel->contextMenuShown() ) {
915  // ### Workaround for bug 56693 (kmail freeze with the complete desktop
916  // ### while pinentry-qt appears)
917  TQByteArray ciphertext( data.msgPart().bodyDecodedBinary() );
918  TQCString cipherStr( ciphertext.data(), ciphertext.size() + 1 );
919  bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) &&
920  (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) &&
921  (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) );
922  if ( !cipherIsBinary ) {
923  decryptedData = cipherStr;
924  }
925  else {
926  decryptedData = "<div style=\"font-size:x-large; text-align:center;"
927  "padding:20pt;\">"
928  + i18n("Encrypted data not shown.").utf8()
929  + "</div>";
930  }
931  }
932 
933  dumpToFile( "dat_05_reader.decrypted", decryptedData.data(), decryptedData.size() );
934 
935  return bDecryptionOk;
936 }
937 
938  //static
939  bool ObjectTreeParser::containsExternalReferences( const TQCString & str )
940  {
941  TQRegExp httpRegExp("(\\\"|\\\'|url\\s*\\(\\s*)http[s]?:");
942  int httpPos = str.find( httpRegExp, 0 );
943 
944  while ( httpPos >= 0 ) {
945  // look backwards for "href"
946  if ( httpPos > 5 ) {
947  int hrefPos = str.findRev( "href", httpPos - 5, true );
948  // if no 'href' is found or the distance between 'href' and '"http[s]:'
949  // is larger than 7 (7 is the distance in 'href = "http[s]:') then
950  // we assume that we have found an external reference
951  if ( ( hrefPos == -1 ) || ( httpPos - hrefPos > 7 ) )
952  return true;
953  }
954  // find next occurrence of "http: or "https:
955  httpPos = str.find( httpRegExp, httpPos + 6 );
956  }
957  return false;
958  }
959 
960  bool ObjectTreeParser::processTextHtmlSubtype( partNode * curNode, ProcessResult & ) {
961  TQCString cstr( curNode->msgPart().bodyDecoded() );
962 
963  mRawReplyString = cstr;
964  if ( curNode->isFirstTextPart() ) {
965  mTextualContent += curNode->msgPart().bodyToUnicode();
966  mTextualContentCharset = curNode->msgPart().charset();
967  }
968 
969  if ( !mReader )
970  return true;
971 
972  if ( curNode->isFirstTextPart() ||
973  attachmentStrategy()->defaultDisplay( curNode ) == AttachmentStrategy::Inline ||
974  showOnlyOneMimePart() )
975  {
976  if ( mReader->htmlMail() ) {
977  curNode->setDisplayedEmbedded( true );
978  // ---Sven's strip </BODY> and </HTML> from end of attachment start-
979  // We must fo this, or else we will see only 1st inlined html
980  // attachment. It is IMHO enough to search only for </BODY> and
981  // put \0 there.
982  int i = cstr.findRev("</body>", -1, false); //case insensitive
983  if ( 0 <= i )
984  cstr.truncate(i);
985  else // just in case - search for </html>
986  {
987  i = cstr.findRev("</html>", -1, false); //case insensitive
988  if ( 0 <= i ) cstr.truncate(i);
989  }
990  // ---Sven's strip </BODY> and </HTML> from end of attachment end-
991  // Show the "external references" warning (with possibility to load
992  // external references only if loading external references is disabled
993  // and the HTML code contains obvious external references). For
994  // messages where the external references are obfuscated the user won't
995  // have an easy way to load them but that shouldn't be a problem
996  // because only spam contains obfuscated external references.
997  if ( !mReader->htmlLoadExternal() &&
998  containsExternalReferences( cstr ) ) {
999  htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
1000  htmlWriter()->queue( i18n("<b>Note:</b> This HTML message may contain external "
1001  "references to images etc. For security/privacy reasons "
1002  "external references are not loaded. If you trust the "
1003  "sender of this message then you can load the external "
1004  "references for this message "
1005  "<a href=\"kmail:loadExternal\">by clicking here</a>.") );
1006  htmlWriter()->queue( "</div><br><br>" );
1007  }
1008  } else {
1009  htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
1010  htmlWriter()->queue( i18n("<b>Note:</b> This is an HTML message. For "
1011  "security reasons, only the raw HTML code "
1012  "is shown. If you trust the sender of this "
1013  "message then you can activate formatted "
1014  "HTML display for this message "
1015  "<a href=\"kmail:showHTML\">by clicking here</a>.") );
1016  htmlWriter()->queue( "</div><br><br>" );
1017  }
1018  htmlWriter()->queue( codecFor( curNode )->toUnicode( mReader->htmlMail() ? cstr : KMMessage::html2source( cstr )));
1019  mReader->mColorBar->setHtmlMode();
1020  return true;
1021  }
1022  return false;
1023  }
1024 } // namespace KMail
1025 
1026 static bool isMailmanMessage( partNode * curNode ) {
1027  if ( !curNode->dwPart() || !curNode->dwPart()->hasHeaders() )
1028  return false;
1029  DwHeaders & headers = curNode->dwPart()->Headers();
1030  if ( headers.HasField("X-Mailman-Version") )
1031  return true;
1032  if ( headers.HasField("X-Mailer") &&
1033  0 == TQCString( headers.FieldBody("X-Mailer").AsString().c_str() )
1034  .find("MAILMAN", 0, false) )
1035  return true;
1036  return false;
1037 }
1038 
1039 namespace KMail {
1040 
1041  bool ObjectTreeParser::processMailmanMessage( partNode * curNode ) {
1042  const TQCString cstr = curNode->msgPart().bodyDecoded();
1043 
1044  //###
1045  const TQCString delim1( "--__--__--\n\nMessage:");
1046  const TQCString delim2( "--__--__--\r\n\r\nMessage:");
1047  const TQCString delimZ2("--__--__--\n\n_____________");
1048  const TQCString delimZ1("--__--__--\r\n\r\n_____________");
1049  TQCString partStr, digestHeaderStr;
1050  int thisDelim = cstr.find(delim1.data(), 0, false);
1051  if ( thisDelim == -1 )
1052  thisDelim = cstr.find(delim2.data(), 0, false);
1053  if ( thisDelim == -1 ) {
1054  kdDebug(5006) << " Sorry: Old style Mailman message but no delimiter found." << endl;
1055  return false;
1056  }
1057 
1058  int nextDelim = cstr.find(delim1.data(), thisDelim+1, false);
1059  if ( -1 == nextDelim )
1060  nextDelim = cstr.find(delim2.data(), thisDelim+1, false);
1061  if ( -1 == nextDelim )
1062  nextDelim = cstr.find(delimZ1.data(), thisDelim+1, false);
1063  if ( -1 == nextDelim )
1064  nextDelim = cstr.find(delimZ2.data(), thisDelim+1, false);
1065  if ( nextDelim < 0)
1066  return false;
1067 
1068  //kdDebug(5006) << " processing old style Mailman digest" << endl;
1069  //if ( curNode->mRoot )
1070  // curNode = curNode->mRoot;
1071 
1072  // at least one message found: build a mime tree
1073  digestHeaderStr = "Content-Type=text/plain\nContent-Description=digest header\n\n";
1074  digestHeaderStr += cstr.mid( 0, thisDelim );
1075  insertAndParseNewChildNode( *curNode,
1076  &*digestHeaderStr,
1077  "Digest Header", true );
1078  //mReader->queueHtml("<br><hr><br>");
1079  // temporarily change curent node's Content-Type
1080  // to get our embedded RfC822 messages properly inserted
1081  curNode->setType( DwMime::kTypeMultipart );
1082  curNode->setSubType( DwMime::kSubtypeDigest );
1083  while( -1 < nextDelim ){
1084  int thisEoL = cstr.find("\nMessage:", thisDelim, false);
1085  if ( -1 < thisEoL )
1086  thisDelim = thisEoL+1;
1087  else{
1088  thisEoL = cstr.find("\n_____________", thisDelim, false);
1089  if ( -1 < thisEoL )
1090  thisDelim = thisEoL+1;
1091  }
1092  thisEoL = cstr.find('\n', thisDelim);
1093  if ( -1 < thisEoL )
1094  thisDelim = thisEoL+1;
1095  else
1096  thisDelim = thisDelim+1;
1097  //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] )
1098  // ++thisDelim;
1099 
1100  partStr = "Content-Type=message/rfc822\nContent-Description=embedded message\n";
1101  partStr += cstr.mid( thisDelim, nextDelim-thisDelim );
1102  TQCString subject("embedded message");
1103  TQCString subSearch("\nSubject:");
1104  int subPos = partStr.find(subSearch.data(), 0, false);
1105  if ( -1 < subPos ){
1106  subject = partStr.mid(subPos+subSearch.length());
1107  thisEoL = subject.find('\n');
1108  if ( -1 < thisEoL )
1109  subject.truncate( thisEoL );
1110  }
1111  //kdDebug(5006) << " embedded message found: \"" << subject << "\"" << endl;
1112  insertAndParseNewChildNode( *curNode,
1113  &*partStr,
1114  subject, true );
1115  //mReader->queueHtml("<br><hr><br>");
1116  thisDelim = nextDelim+1;
1117  nextDelim = cstr.find(delim1.data(), thisDelim, false);
1118  if ( -1 == nextDelim )
1119  nextDelim = cstr.find(delim2.data(), thisDelim, false);
1120  if ( -1 == nextDelim )
1121  nextDelim = cstr.find(delimZ1.data(), thisDelim, false);
1122  if ( -1 == nextDelim )
1123  nextDelim = cstr.find(delimZ2.data(), thisDelim, false);
1124  }
1125  // reset curent node's Content-Type
1126  curNode->setType( DwMime::kTypeText );
1127  curNode->setSubType( DwMime::kSubtypePlain );
1128  int thisEoL = cstr.find("_____________", thisDelim);
1129  if ( -1 < thisEoL ){
1130  thisDelim = thisEoL;
1131  thisEoL = cstr.find('\n', thisDelim);
1132  if ( -1 < thisEoL )
1133  thisDelim = thisEoL+1;
1134  }
1135  else
1136  thisDelim = thisDelim+1;
1137  partStr = "Content-Type=text/plain\nContent-Description=digest footer\n\n";
1138  partStr += cstr.mid( thisDelim );
1139  insertAndParseNewChildNode( *curNode,
1140  &*partStr,
1141  "Digest Footer", true );
1142  return true;
1143  }
1144 
1145  bool ObjectTreeParser::processTextPlainSubtype( partNode * curNode, ProcessResult & result ) {
1146  if ( !mReader ) {
1147  mRawReplyString = curNode->msgPart().bodyDecoded();
1148  if ( curNode->isFirstTextPart() ) {
1149  mTextualContent += curNode->msgPart().bodyToUnicode();
1150  mTextualContentCharset = curNode->msgPart().charset();
1151  }
1152  return true;
1153  }
1154 
1155  if ( !curNode->isFirstTextPart() &&
1156  attachmentStrategy()->defaultDisplay( curNode ) != AttachmentStrategy::Inline &&
1157  !showOnlyOneMimePart() )
1158  return false;
1159 
1160  mRawReplyString = curNode->msgPart().bodyDecoded();
1161  if ( curNode->isFirstTextPart() ) {
1162  mTextualContent += curNode->msgPart().bodyToUnicode();
1163  mTextualContentCharset = curNode->msgPart().charset();
1164  }
1165 
1166  TQString label = curNode->msgPart().fileName().stripWhiteSpace();
1167  if ( label.isEmpty() )
1168  label = curNode->msgPart().name().stripWhiteSpace();
1169 
1170  const bool bDrawFrame = !curNode->isFirstTextPart()
1171  && !showOnlyOneMimePart()
1172  && !label.isEmpty();
1173  if ( bDrawFrame ) {
1174  label = KMMessage::quoteHtmlChars( label, true );
1175 
1176  const TQString comment =
1177  KMMessage::quoteHtmlChars( curNode->msgPart().contentDescription(), true );
1178 
1179  const TQString fileName =
1180  mReader->writeMessagePartToTempFile( &curNode->msgPart(),
1181  curNode->nodeId() );
1182 
1183  const TQString dir = TQApplication::reverseLayout() ? "rtl" : "ltr" ;
1184 
1185  TQString htmlStr = "<table cellspacing=\"1\" class=\"textAtm\">"
1186  "<tr class=\"textAtmH\"><td dir=\"" + dir + "\">";
1187  if ( !fileName.isEmpty() )
1188  htmlStr += "<a href=\"" + curNode->asHREF( "body" ) + "\">"
1189  + label + "</a>";
1190  else
1191  htmlStr += label;
1192  if ( !comment.isEmpty() )
1193  htmlStr += "<br>" + comment;
1194  htmlStr += "</td></tr><tr class=\"textAtmB\"><td>";
1195 
1196  htmlWriter()->queue( htmlStr );
1197  }
1198  // process old style not-multipart Mailman messages to
1199  // enable verification of the embedded messages' signatures
1200  if ( !isMailmanMessage( curNode ) ||
1201  !processMailmanMessage( curNode ) ) {
1202  writeBodyString( mRawReplyString, curNode->trueFromAddress(),
1203  codecFor( curNode ), result, !bDrawFrame );
1204  curNode->setDisplayedEmbedded( true );
1205  }
1206  if ( bDrawFrame )
1207  htmlWriter()->queue( "</td></tr></table>" );
1208 
1209  return true;
1210  }
1211 
1212  void ObjectTreeParser::stdChildHandling( partNode * child ) {
1213  if ( !child )
1214  return;
1215 
1216  ObjectTreeParser otp( *this );
1217  otp.setShowOnlyOneMimePart( false );
1218  otp.parseObjectTree( child );
1219  mRawReplyString += otp.rawReplyString();
1220  mTextualContent += otp.textualContent();
1221  if ( !otp.textualContentCharset().isEmpty() )
1222  mTextualContentCharset = otp.textualContentCharset();
1223  }
1224 
1225  TQString ObjectTreeParser::defaultToltecReplacementText()
1226  {
1227  return i18n( "This message is a <i>Toltec</i> Groupware object, it can only be viewed with "
1228  "Microsoft Outlook in combination with the Toltec connector." );
1229  }
1230 
1231  bool ObjectTreeParser::processToltecMail( partNode *node )
1232  {
1233  if ( !node || !mHtmlWriter || !GlobalSettings::self()->showToltecReplacementText() ||
1234  !node->isToltecMessage() || mShowRawToltecMail )
1235  return false;
1236 
1237  htmlWriter()->queue( GlobalSettings::self()->toltecReplacementText() );
1238  htmlWriter()->queue( "<br><br><a href=\"kmail:showRawToltecMail\">" +
1239  i18n( "Show Raw Message" ) + "</a>" );
1240  return true;
1241  }
1242 
1243  bool ObjectTreeParser::processMultiPartMixedSubtype( partNode * node, ProcessResult & ) {
1244 
1245  if ( processToltecMail( node ) ) {
1246  return true;
1247  }
1248 
1249  partNode * child = node->firstChild();
1250  if ( !child )
1251  return false;
1252 
1253  // normal treatment of the parts in the mp/mixed container
1254  stdChildHandling( child );
1255  return true;
1256  }
1257 
1258  bool ObjectTreeParser::processMultiPartAlternativeSubtype( partNode * node, ProcessResult & ) {
1259  partNode * child = node->firstChild();
1260  if ( !child )
1261  return false;
1262 
1263  partNode * dataHtml = child->findType( DwMime::kTypeText,
1264  DwMime::kSubtypeHtml, false, true );
1265  partNode * dataPlain = child->findType( DwMime::kTypeText,
1266  DwMime::kSubtypePlain, false, true );
1267 
1268  if ( (mReader && mReader->htmlMail() && dataHtml) ||
1269  (dataHtml && dataPlain && dataPlain->msgPart().body().isEmpty()) ) {
1270  if ( dataPlain )
1271  dataPlain->setProcessed( true, false );
1272  stdChildHandling( dataHtml );
1273  return true;
1274  }
1275 
1276  if ( !mReader || (!mReader->htmlMail() && dataPlain) ) {
1277  if ( dataHtml )
1278  dataHtml->setProcessed( true, false );
1279  stdChildHandling( dataPlain );
1280  return true;
1281  }
1282 
1283  stdChildHandling( child );
1284  return true;
1285  }
1286 
1287  bool ObjectTreeParser::processMultiPartDigestSubtype( partNode * node, ProcessResult & result ) {
1288  return processMultiPartMixedSubtype( node, result );
1289  }
1290 
1291  bool ObjectTreeParser::processMultiPartParallelSubtype( partNode * node, ProcessResult & result ) {
1292  return processMultiPartMixedSubtype( node, result );
1293  }
1294 
1295  bool ObjectTreeParser::processMultiPartSignedSubtype( partNode * node, ProcessResult & ) {
1296  if ( node->childCount() != 2 ) {
1297  kdDebug(5006) << "mulitpart/signed must have exactly two child parts!" << endl
1298  << "processing as multipart/mixed" << endl;
1299  if ( node->firstChild() )
1300  stdChildHandling( node->firstChild() );
1301  return node->firstChild();
1302  }
1303 
1304  partNode * signedData = node->firstChild();
1305  assert( signedData );
1306 
1307  partNode * signature = signedData->nextSibling();
1308  assert( signature );
1309 
1310  signature->setProcessed( true, true );
1311 
1312  if ( !includeSignatures() ) {
1313  stdChildHandling( signedData );
1314  return true;
1315  }
1316 
1317  // FIXME(marc) check here that the protocol parameter matches the
1318  // mimetype of "signature" (not required by the RFC, but practised
1319  // by all implementaions of security multiparts
1320 
1321  const TQString contentType = node->contentTypeParameter( "protocol" ).lower();
1322  const Kleo::CryptoBackend::Protocol *protocol = 0;
1323  if ( contentType == "application/pkcs7-signature" || contentType == "application/x-pkcs7-signature" )
1324  protocol = Kleo::CryptoBackendFactory::instance()->smime();
1325  else if ( contentType == "application/pgp-signature" || contentType == "application/x-pgp-signature" )
1326  protocol = Kleo::CryptoBackendFactory::instance()->openpgp();
1327 
1328  if ( !protocol ) {
1329  signature->setProcessed( true, true );
1330  stdChildHandling( signedData );
1331  return true;
1332  }
1333 
1334  CryptoProtocolSaver saver( this, protocol );
1335 
1336  node->setSignatureState( KMMsgFullySigned );
1337  writeOpaqueOrMultipartSignedData( signedData, *signature,
1338  node->trueFromAddress() );
1339  return true;
1340  }
1341 
1342  bool ObjectTreeParser::processMultiPartEncryptedSubtype( partNode * node, ProcessResult & result ) {
1343  partNode * child = node->firstChild();
1344  if ( !child )
1345  return false;
1346 
1347  if ( keepEncryptions() ) {
1348  node->setEncryptionState( KMMsgFullyEncrypted );
1349  const TQCString cstr = node->msgPart().bodyDecoded();
1350  if ( mReader )
1351  writeBodyString( cstr, node->trueFromAddress(),
1352  codecFor( node ), result, false );
1353  mRawReplyString += cstr;
1354  return true;
1355  }
1356 
1357  const Kleo::CryptoBackend::Protocol * useThisCryptProto = 0;
1358 
1359  /*
1360  ATTENTION: This code is to be replaced by the new 'auto-detect' feature. --------------------------------------
1361  */
1362  partNode * data = child->findType( DwMime::kTypeApplication,
1363  DwMime::kSubtypeOctetStream, false, true );
1364  if ( data ) {
1365  useThisCryptProto = Kleo::CryptoBackendFactory::instance()->openpgp();
1366  }
1367  if ( !data ) {
1368  data = child->findType( DwMime::kTypeApplication,
1369  DwMime::kSubtypePkcs7Mime, false, true );
1370  if ( data ) {
1371  useThisCryptProto = Kleo::CryptoBackendFactory::instance()->smime();
1372  }
1373  }
1374  /*
1375  ---------------------------------------------------------------------------------------------------------------
1376  */
1377 
1378  if ( !data ) {
1379  stdChildHandling( child );
1380  return true;
1381  }
1382 
1383  CryptoProtocolSaver cpws( this, useThisCryptProto );
1384 
1385  if ( partNode * dataChild = data->firstChild() ) {
1386  //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl;
1387  stdChildHandling( dataChild );
1388  //kdDebug(5006) << "\n-----> Returning from parseObjectTree( curNode->mChild )\n" << endl;
1389  return true;
1390  }
1391 
1392  node->setEncryptionState( KMMsgFullyEncrypted );
1393 
1394  if ( mReader && !mReader->decryptMessage() ) {
1395  writeDeferredDecryptionBlock();
1396  data->setProcessed( true, false ); // Set the data node to done to prevent it from being processed
1397  return true;
1398  }
1399 
1400  //kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl;
1401  PartMetaData messagePart;
1402  TQCString decryptedData;
1403  bool signatureFound;
1404  std::vector<GpgME::Signature> signatures;
1405  bool passphraseError;
1406  bool actuallyEncrypted = true;
1407  bool decryptionStarted;
1408 
1409  bool bOkDecrypt = okDecryptMIME( *data,
1410  decryptedData,
1411  signatureFound,
1412  signatures,
1413  true,
1414  passphraseError,
1415  actuallyEncrypted,
1416  decryptionStarted,
1417  messagePart.errorText,
1418  messagePart.auditLogError,
1419  messagePart.auditLog );
1420 
1421  if ( decryptionStarted ) {
1422  writeDecryptionInProgressBlock();
1423  return true;
1424  }
1425 
1426  // paint the frame
1427  if ( mReader ) {
1428  messagePart.isDecryptable = bOkDecrypt;
1429  messagePart.isEncrypted = true;
1430  messagePart.isSigned = false;
1431  htmlWriter()->queue( writeSigstatHeader( messagePart,
1432  cryptoProtocol(),
1433  node->trueFromAddress() ) );
1434  }
1435 
1436  if ( bOkDecrypt ) {
1437  // Note: Multipart/Encrypted might also be signed
1438  // without encapsulating a nicely formatted
1439  // ~~~~~~~ Multipart/Signed part.
1440  // (see RFC 3156 --> 6.2)
1441  // In this case we paint a _2nd_ frame inside the
1442  // encryption frame, but we do _not_ show a respective
1443  // encapsulated MIME part in the Mime Tree Viewer
1444  // since we do want to show the _true_ structure of the
1445  // message there - not the structure that the sender's
1446  // MUA 'should' have sent. :-D (khz, 12.09.2002)
1447  //
1448  if ( signatureFound ) {
1449  writeOpaqueOrMultipartSignedData( 0,
1450  *node,
1451  node->trueFromAddress(),
1452  false,
1453  &decryptedData,
1454  signatures,
1455  false );
1456  node->setSignatureState( KMMsgFullySigned );
1457  } else {
1458  insertAndParseNewChildNode( *node,
1459  &*decryptedData,
1460  "encrypted data" );
1461  }
1462  } else {
1463  mRawReplyString += decryptedData;
1464  if ( mReader ) {
1465  // print the error message that was returned in decryptedData
1466  // (utf8-encoded)
1467  htmlWriter()->queue( TQString::fromUtf8( decryptedData.data() ) );
1468  }
1469  }
1470 
1471  if ( mReader )
1472  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1473  data->setProcessed( true, false ); // Set the data node to done to prevent it from being processed
1474  return true;
1475  }
1476 
1477 
1478  bool ObjectTreeParser::processMessageRfc822Subtype( partNode * node, ProcessResult & ) {
1479  if ( mReader
1480  && !attachmentStrategy()->inlineNestedMessages()
1481  && !showOnlyOneMimePart() )
1482  return false;
1483 
1484  if ( partNode * child = node->firstChild() ) {
1485  //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl;
1486  ObjectTreeParser otp( mReader, cryptoProtocol() );
1487  otp.parseObjectTree( child );
1488  mRawReplyString += otp.rawReplyString();
1489  mTextualContent += otp.textualContent();
1490  if ( !otp.textualContentCharset().isEmpty() )
1491  mTextualContentCharset = otp.textualContentCharset();
1492  //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl;
1493  return true;
1494  }
1495  //kdDebug(5006) << "\n-----> Initially processing data of embedded RfC 822 message\n" << endl;
1496  // paint the frame
1497  PartMetaData messagePart;
1498  if ( mReader ) {
1499  messagePart.isEncrypted = false;
1500  messagePart.isSigned = false;
1501  messagePart.isEncapsulatedRfc822Message = true;
1502  TQString filename =
1503  mReader->writeMessagePartToTempFile( &node->msgPart(),
1504  node->nodeId() );
1505  htmlWriter()->queue( writeSigstatHeader( messagePart,
1506  cryptoProtocol(),
1507  node->trueFromAddress(),
1508  node ) );
1509  }
1510  TQCString rfc822messageStr( node->msgPart().bodyDecoded() );
1511  // display the headers of the encapsulated message
1512  DwMessage* rfc822DwMessage = new DwMessage(); // will be deleted by c'tor of rfc822headers
1513  rfc822DwMessage->FromString( rfc822messageStr );
1514  rfc822DwMessage->Parse();
1515  KMMessage rfc822message( rfc822DwMessage );
1516  node->setFromAddress( rfc822message.from() );
1517  //kdDebug(5006) << "\n-----> Store RfC 822 message header \"From: " << rfc822message.from() << "\"\n" << endl;
1518  if ( mReader )
1519  htmlWriter()->queue( mReader->writeMsgHeader( &rfc822message ) );
1520  //mReader->parseMsgHeader( &rfc822message );
1521  // display the body of the encapsulated message
1522  insertAndParseNewChildNode( *node,
1523  &*rfc822messageStr,
1524  "encapsulated message", false /*append*/,
1525  false /*add to textual content*/ );
1526  node->setDisplayedEmbedded( true );
1527  if ( mReader )
1528  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1529  return true;
1530  }
1531 
1532 
1533  bool ObjectTreeParser::processApplicationOctetStreamSubtype( partNode * node, ProcessResult & result ) {
1534  if ( partNode * child = node->firstChild() ) {
1535  //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl;
1536  ObjectTreeParser otp( mReader, cryptoProtocol() );
1537  otp.parseObjectTree( child );
1538  mRawReplyString += otp.rawReplyString();
1539  mTextualContent += otp.textualContent();
1540  if ( !otp.textualContentCharset().isEmpty() )
1541  mTextualContentCharset = otp.textualContentCharset();
1542  //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl;
1543  return true;
1544  }
1545 
1546  const Kleo::CryptoBackend::Protocol* oldUseThisCryptPlug = cryptoProtocol();
1547  if ( node->parentNode()
1548  && DwMime::kTypeMultipart == node->parentNode()->type()
1549  && DwMime::kSubtypeEncrypted == node->parentNode()->subType() ) {
1550  //kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl;
1551  node->setEncryptionState( KMMsgFullyEncrypted );
1552  if ( keepEncryptions() ) {
1553  const TQCString cstr = node->msgPart().bodyDecoded();
1554  if ( mReader )
1555  writeBodyString( cstr, node->trueFromAddress(),
1556  codecFor( node ), result, false );
1557  mRawReplyString += cstr;
1558  } else if ( mReader && !mReader->decryptMessage() ) {
1559  writeDeferredDecryptionBlock();
1560  } else {
1561  /*
1562  ATTENTION: This code is to be replaced by the planned 'auto-detect' feature.
1563  */
1564  PartMetaData messagePart;
1565  setCryptoProtocol( Kleo::CryptoBackendFactory::instance()->openpgp() );
1566  TQCString decryptedData;
1567  bool signatureFound;
1568  std::vector<GpgME::Signature> signatures;
1569  bool passphraseError;
1570  bool actuallyEncrypted = true;
1571  bool decryptionStarted;
1572 
1573  bool bOkDecrypt = okDecryptMIME( *node,
1574  decryptedData,
1575  signatureFound,
1576  signatures,
1577  true,
1578  passphraseError,
1579  actuallyEncrypted,
1580  decryptionStarted,
1581  messagePart.errorText,
1582  messagePart.auditLogError,
1583  messagePart.auditLog );
1584 
1585  if ( decryptionStarted ) {
1586  writeDecryptionInProgressBlock();
1587  return true;
1588  }
1589 
1590  // paint the frame
1591  if ( mReader ) {
1592  messagePart.isDecryptable = bOkDecrypt;
1593  messagePart.isEncrypted = true;
1594  messagePart.isSigned = false;
1595  htmlWriter()->queue( writeSigstatHeader( messagePart,
1596  cryptoProtocol(),
1597  node->trueFromAddress() ) );
1598  }
1599 
1600  if ( bOkDecrypt ) {
1601  // fixing the missing attachments bug #1090-b
1602  insertAndParseNewChildNode( *node,
1603  &*decryptedData,
1604  "encrypted data" );
1605  } else {
1606  mRawReplyString += decryptedData;
1607  if ( mReader ) {
1608  // print the error message that was returned in decryptedData
1609  // (utf8-encoded)
1610  htmlWriter()->queue( TQString::fromUtf8( decryptedData.data() ) );
1611  }
1612  }
1613 
1614  if ( mReader )
1615  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1616  }
1617  return true;
1618  }
1619  setCryptoProtocol( oldUseThisCryptPlug );
1620  return false;
1621  }
1622 
1623  bool ObjectTreeParser::processApplicationPkcs7MimeSubtype( partNode * node, ProcessResult & result ) {
1624  if ( partNode * child = node->firstChild() ) {
1625  //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl;
1626  ObjectTreeParser otp( mReader, cryptoProtocol() );
1627  otp.parseObjectTree( child );
1628  mRawReplyString += otp.rawReplyString();
1629  mTextualContent += otp.textualContent();
1630  if ( !otp.textualContentCharset().isEmpty() )
1631  mTextualContentCharset = otp.textualContentCharset();
1632  //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl;
1633  return true;
1634  }
1635 
1636  //kdDebug(5006) << "\n-----> Initially processing signed and/or encrypted data\n" << endl;
1637  if ( !node->dwPart() || !node->dwPart()->hasHeaders() )
1638  return false;
1639 
1640  const Kleo::CryptoBackend::Protocol * smimeCrypto = Kleo::CryptoBackendFactory::instance()->smime();
1641 
1642  const TQString smimeType = node->contentTypeParameter("smime-type").lower();
1643 
1644  if ( smimeType == "certs-only" ) {
1645  result.setNeverDisplayInline( true );
1646  if ( !smimeCrypto || !mReader )
1647  return false;
1648 
1649  const TDEConfigGroup reader( KMKernel::config(), "Reader" );
1650  if ( !reader.readBoolEntry( "AutoImportKeys", false ) )
1651  return false;
1652 
1653  const TQByteArray certData = node->msgPart().bodyDecodedBinary();
1654 
1655  const STD_NAMESPACE_PREFIX unique_ptr<Kleo::ImportJob> import( smimeCrypto->importJob() );
1656  const GpgME::ImportResult res = import->exec( certData );
1657  if ( res.error() ) {
1658  htmlWriter()->queue( i18n( "Sorry, certificate could not be imported.<br>"
1659  "Reason: %1").arg( TQString::fromLocal8Bit( res.error().asString() ) ) );
1660  return true;
1661  }
1662 
1663  const int nImp = res.numImported();
1664  const int nUnc = res.numUnchanged();
1665  const int nSKImp = res.numSecretKeysImported();
1666  const int nSKUnc = res.numSecretKeysUnchanged();
1667  if ( !nImp && !nSKImp && !nUnc && !nSKUnc ) {
1668  htmlWriter()->queue( i18n( "Sorry, no certificates were found in this message." ) );
1669  return true;
1670  }
1671  TQString comment = "<b>" + i18n( "Certificate import status:" ) + "</b><br>&nbsp;<br>";
1672  if ( nImp )
1673  comment += i18n( "1 new certificate was imported.",
1674  "%n new certificates were imported.", nImp ) + "<br>";
1675  if ( nUnc )
1676  comment += i18n( "1 certificate was unchanged.",
1677  "%n certificates were unchanged.", nUnc ) + "<br>";
1678  if ( nSKImp )
1679  comment += i18n( "1 new secret key was imported.",
1680  "%n new secret keys were imported.", nSKImp ) + "<br>";
1681  if ( nSKUnc )
1682  comment += i18n( "1 secret key was unchanged.",
1683  "%n secret keys were unchanged.", nSKUnc ) + "<br>";
1684  comment += "&nbsp;<br>";
1685  htmlWriter()->queue( comment );
1686  if ( !nImp && !nSKImp ) {
1687  htmlWriter()->queue( "<hr>" );
1688  return true;
1689  }
1690  const std::vector<GpgME::Import> imports = res.imports();
1691  if ( imports.empty() ) {
1692  htmlWriter()->queue( i18n( "Sorry, no details on certificate import available." ) + "<hr>" );
1693  return true;
1694  }
1695  htmlWriter()->queue( "<b>" + i18n( "Certificate import details:" ) + "</b><br>" );
1696  for ( std::vector<GpgME::Import>::const_iterator it = imports.begin() ; it != imports.end() ; ++it ) {
1697  if ( (*it).error() )
1698  htmlWriter()->queue( i18n( "Failed: %1 (%2)" )
1699  .arg( (*it).fingerprint(),
1700  TQString::fromLocal8Bit( (*it).error().asString() ) ) );
1701  else if ( (*it).status() & ~GpgME::Import::ContainedSecretKey ) {
1702  if ( (*it).status() & GpgME::Import::ContainedSecretKey ) {
1703  htmlWriter()->queue( i18n( "New or changed: %1 (secret key available)" ).arg( (*it).fingerprint() ) );
1704  }
1705  else {
1706  htmlWriter()->queue( i18n( "New or changed: %1" ).arg( (*it).fingerprint() ) );
1707  }
1708  }
1709  htmlWriter()->queue( "<br>" );
1710  }
1711 
1712  htmlWriter()->queue( "<hr>" );
1713  return true;
1714  }
1715 
1716  if ( !smimeCrypto )
1717  return false;
1718  CryptoProtocolSaver cpws( this, smimeCrypto );
1719 
1720  bool isSigned = smimeType == "signed-data";
1721  bool isEncrypted = smimeType == "enveloped-data";
1722 
1723  // Analyze "signTestNode" node to find/verify a signature.
1724  // If zero this verification was successfully done after
1725  // decrypting via recursion by insertAndParseNewChildNode().
1726  partNode* signTestNode = isEncrypted ? 0 : node;
1727 
1728 
1729  // We try decrypting the content
1730  // if we either *know* that it is an encrypted message part
1731  // or there is neither signed nor encrypted parameter.
1732  if ( !isSigned ) {
1733  if ( isEncrypted ) {
1734  //kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data" << endl;
1735  }
1736  else {
1737  //kdDebug(5006) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?" << endl;
1738  }
1739  TQCString decryptedData;
1740  PartMetaData messagePart;
1741  messagePart.isEncrypted = true;
1742  messagePart.isSigned = false;
1743  bool signatureFound;
1744  std::vector<GpgME::Signature> signatures;
1745  bool passphraseError;
1746  bool actuallyEncrypted = true;
1747  bool decryptionStarted;
1748 
1749  if ( mReader && !mReader->decryptMessage() ) {
1750  writeDeferredDecryptionBlock();
1751  isEncrypted = true;
1752  signTestNode = 0; // PENDING(marc) to be abs. sure, we'd need to have to look at the content
1753  } else {
1754  const bool bOkDecrypt = okDecryptMIME( *node,
1755  decryptedData,
1756  signatureFound,
1757  signatures,
1758  false,
1759  passphraseError,
1760  actuallyEncrypted,
1761  decryptionStarted,
1762  messagePart.errorText,
1763  messagePart.auditLogError,
1764  messagePart.auditLog );
1765  if ( decryptionStarted ) {
1766  writeDecryptionInProgressBlock();
1767  return true;
1768  }
1769  if ( bOkDecrypt ) {
1770  //kdDebug(5006) << "pkcs7 mime - encryption found - enveloped (encrypted) data !" << endl;
1771  isEncrypted = true;
1772  node->setEncryptionState( KMMsgFullyEncrypted );
1773  signTestNode = 0;
1774  // paint the frame
1775  messagePart.isDecryptable = true;
1776  if ( mReader )
1777  htmlWriter()->queue( writeSigstatHeader( messagePart,
1778  cryptoProtocol(),
1779  node->trueFromAddress() ) );
1780  insertAndParseNewChildNode( *node,
1781  &*decryptedData,
1782  "encrypted data" );
1783  if ( mReader )
1784  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1785  } else {
1786  // decryption failed, which could be because the part was encrypted but
1787  // decryption failed, or because we didn't know if it was encrypted, tried,
1788  // and failed. If the message was not actually encrypted, we continue
1789  // assuming it's signed
1790  if ( passphraseError || ( smimeType.isEmpty() && actuallyEncrypted ) ) {
1791  isEncrypted = true;
1792  signTestNode = 0;
1793  }
1794 
1795  if ( isEncrypted ) {
1796  //kdDebug(5006) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !" << endl;
1797  // paint the frame
1798  messagePart.isDecryptable = false;
1799  if ( mReader ) {
1800  htmlWriter()->queue( writeSigstatHeader( messagePart,
1801  cryptoProtocol(),
1802  node->trueFromAddress() ) );
1803  assert( mReader->decryptMessage() ); // handled above
1804  writePartIcon( &node->msgPart(), node->nodeId() );
1805  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1806  }
1807  } else {
1808  //kdDebug(5006) << "pkcs7 mime - NO encryption found" << endl;
1809  }
1810  }
1811  }
1812  if ( isEncrypted )
1813  node->setEncryptionState( KMMsgFullyEncrypted );
1814  }
1815 
1816  // We now try signature verification if necessarry.
1817  if ( signTestNode ) {
1818  if ( isSigned ) {
1819  //kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: opaque signed data" << endl;
1820  }
1821  else {
1822  //kdDebug(5006) << "pkcs7 mime - type unknown - opaque signed data ?" << endl;
1823  }
1824 
1825  bool sigFound = writeOpaqueOrMultipartSignedData( 0,
1826  *signTestNode,
1827  node->trueFromAddress(),
1828  true,
1829  0,
1830  std::vector<GpgME::Signature>(),
1831  isEncrypted );
1832  if ( sigFound ) {
1833  if ( !isSigned ) {
1834  //kdDebug(5006) << "pkcs7 mime - signature found - opaque signed data !" << endl;
1835  isSigned = true;
1836  }
1837  signTestNode->setSignatureState( KMMsgFullySigned );
1838  if ( signTestNode != node )
1839  node->setSignatureState( KMMsgFullySigned );
1840  } else {
1841  //kdDebug(5006) << "pkcs7 mime - NO signature found :-(" << endl;
1842  }
1843  }
1844 
1845  return isSigned || isEncrypted;
1846 }
1847 
1848 bool ObjectTreeParser::decryptChiasmus( const TQByteArray& data, TQByteArray& bodyDecoded, TQString& errorText )
1849 {
1850  const Kleo::CryptoBackend::Protocol * chiasmus =
1851  Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
1852  Q_ASSERT( chiasmus );
1853  if ( !chiasmus )
1854  return false;
1855 
1856  const STD_NAMESPACE_PREFIX unique_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", TQStringVariantMap() ) );
1857  if ( !listjob ) {
1858  errorText = i18n( "Chiasmus backend does not offer the "
1859  "\"x-obtain-keys\" function. Please report this bug." );
1860  return false;
1861  }
1862 
1863  if ( listjob->exec() ) {
1864  errorText = i18n( "Chiasmus Backend Error" );
1865  return false;
1866  }
1867 
1868  const TQVariant result = listjob->property( "result" );
1869  if ( result.type() != TQVariant::StringList ) {
1870  errorText = i18n( "Unexpected return value from Chiasmus backend: "
1871  "The \"x-obtain-keys\" function did not return a "
1872  "string list. Please report this bug." );
1873  return false;
1874  }
1875 
1876  const TQStringList keys = result.toStringList();
1877  if ( keys.empty() ) {
1878  errorText = i18n( "No keys have been found. Please check that a "
1879  "valid key path has been set in the Chiasmus "
1880  "configuration." );
1881  return false;
1882  }
1883 
1884  emit mReader->noDrag();
1885  ChiasmusKeySelector selectorDlg( mReader, i18n( "Chiasmus Decryption Key Selection" ),
1886  keys, GlobalSettings::chiasmusDecryptionKey(),
1887  GlobalSettings::chiasmusDecryptionOptions() );
1888  if ( selectorDlg.exec() != TQDialog::Accepted )
1889  return false;
1890 
1891  GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() );
1892  GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() );
1893  assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );
1894 
1895  const STD_NAMESPACE_PREFIX unique_ptr<Kleo::SpecialJob> job( chiasmus->specialJob( "x-decrypt", TQStringVariantMap() ) );
1896  if ( !job ) {
1897  errorText = i18n( "Chiasmus backend does not offer the "
1898  "\"x-decrypt\" function. Please report this bug." );
1899  return false;
1900  }
1901 
1902  if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
1903  !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
1904  !job->setProperty( "input", data ) ) {
1905  errorText = i18n( "The \"x-decrypt\" function does not accept "
1906  "the expected parameters. Please report this bug." );
1907  return false;
1908  }
1909 
1910  if ( job->exec() ) {
1911  errorText = i18n( "Chiasmus Decryption Error" );
1912  return false;
1913  }
1914 
1915  const TQVariant resultData = job->property( "result" );
1916  if ( resultData.type() != TQVariant::ByteArray ) {
1917  errorText = i18n( "Unexpected return value from Chiasmus backend: "
1918  "The \"x-decrypt\" function did not return a "
1919  "byte array. Please report this bug." );
1920  return false;
1921  }
1922  bodyDecoded = resultData.toByteArray();
1923  return true;
1924 }
1925 
1926 bool ObjectTreeParser::processApplicationChiasmusTextSubtype( partNode * curNode, ProcessResult & result )
1927 {
1928  if ( !mReader ) {
1929  mRawReplyString = curNode->msgPart().bodyDecoded();
1930  mTextualContent += curNode->msgPart().bodyToUnicode();
1931  mTextualContentCharset = curNode->msgPart().charset();
1932  return true;
1933  }
1934 
1935  TQByteArray decryptedBody;
1936  TQString errorText;
1937  const TQByteArray data = curNode->msgPart().bodyDecodedBinary();
1938  bool bOkDecrypt = decryptChiasmus( data, decryptedBody, errorText );
1939  PartMetaData messagePart;
1940  messagePart.isDecryptable = bOkDecrypt;
1941  messagePart.isEncrypted = true;
1942  messagePart.isSigned = false;
1943  messagePart.errorText = errorText;
1944  if ( mReader )
1945  htmlWriter()->queue( writeSigstatHeader( messagePart,
1946  0, //cryptPlugWrapper(),
1947  curNode->trueFromAddress() ) );
1948  const TQByteArray body = bOkDecrypt ? decryptedBody : data;
1949  const TQString chiasmusCharset = curNode->contentTypeParameter("chiasmus-charset");
1950  const TQTextCodec* aCodec = chiasmusCharset.isEmpty()
1951  ? codecFor( curNode )
1952  : KMMsgBase::codecForName( chiasmusCharset.ascii() );
1953  htmlWriter()->queue( quotedHTML( aCodec->toUnicode( body ), false /*decorate*/ ) );
1954  result.setInlineEncryptionState( KMMsgFullyEncrypted );
1955  if ( mReader )
1956  htmlWriter()->queue( writeSigstatFooter( messagePart ) );
1957  return true;
1958 }
1959 
1960 bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessResult &result )
1961 {
1962  Q_UNUSED( result );
1963  if ( !mReader )
1964  return false;
1965 
1966  const TQString fileName = mReader->writeMessagePartToTempFile( &node->msgPart(), node->nodeId() );
1967  KTNEFParser parser;
1968  if ( !parser.openFile( fileName ) || !parser.message()) {
1969  kdDebug() << k_funcinfo << "Could not parse " << fileName << endl;
1970  return false;
1971  }
1972 
1973  TQPtrList<KTNEFAttach> tnefatts = parser.message()->attachmentList();
1974  if ( tnefatts.isEmpty() ) {
1975  kdDebug() << k_funcinfo << "No attachments found in " << fileName << endl;
1976  return false;
1977  }
1978 
1979  if ( !showOnlyOneMimePart() ) {
1980  TQString label = node->msgPart().fileName().stripWhiteSpace();
1981  if ( label.isEmpty() )
1982  label = node->msgPart().name().stripWhiteSpace();
1983  label = KMMessage::quoteHtmlChars( label, true );
1984  const TQString comment = KMMessage::quoteHtmlChars( node->msgPart().contentDescription(), true );
1985  const TQString dir = TQApplication::reverseLayout() ? "rtl" : "ltr" ;
1986 
1987  TQString htmlStr = "<table cellspacing=\"1\" class=\"textAtm\">"
1988  "<tr class=\"textAtmH\"><td dir=\"" + dir + "\">";
1989  if ( !fileName.isEmpty() )
1990  htmlStr += "<a href=\"" + node->asHREF( "body" ) + "\">"
1991  + label + "</a>";
1992  else
1993  htmlStr += label;
1994  if ( !comment.isEmpty() )
1995  htmlStr += "<br>" + comment;
1996  htmlStr += "</td></tr><tr class=\"textAtmB\"><td>";
1997  htmlWriter()->queue( htmlStr );
1998  }
1999 
2000  for ( uint i = 0; i < tnefatts.count(); ++i ) {
2001  KTNEFAttach *att = tnefatts.at( i );
2002  TQString label = att->displayName();
2003  if( label.isEmpty() )
2004  label = att->name();
2005  label = KMMessage::quoteHtmlChars( label, true );
2006 
2007  TQString dir = mReader->createTempDir( "ktnef-" + TQString::number( i ) );
2008  parser.extractFileTo( att->name(), dir );
2009  mReader->mTempFiles.append( dir + TQDir::separator() + att->name() );
2010  TQString href = "file:" + KURL::encode_string( dir + TQDir::separator() + att->name() );
2011 
2012  KMimeType::Ptr mimeType = KMimeType::mimeType( att->mimeTag() );
2013  TQString iconName = TDEGlobal::instance()->iconLoader()->iconPath( mimeType->icon( TQString(), false ), TDEIcon::Desktop );
2014 
2015  htmlWriter()->queue( "<div><a href=\"" + href + "\"><img src=\"" +
2016  iconName + "\" border=\"0\" style=\"max-width: 100%\">" + label +
2017  "</a></div><br>" );
2018  }
2019 
2020  if ( !showOnlyOneMimePart() )
2021  htmlWriter()->queue( "</td></tr></table>" );
2022 
2023  return true;
2024 }
2025 
2026  void ObjectTreeParser::writeBodyString( const TQCString & bodyString,
2027  const TQString & fromAddress,
2028  const TQTextCodec * codec,
2029  ProcessResult & result,
2030  bool decorate ) {
2031  assert( mReader ); assert( codec );
2032  KMMsgSignatureState inlineSignatureState = result.inlineSignatureState();
2033  KMMsgEncryptionState inlineEncryptionState = result.inlineEncryptionState();
2034  writeBodyStr( bodyString, codec, fromAddress,
2035  inlineSignatureState, inlineEncryptionState, decorate );
2036  result.setInlineSignatureState( inlineSignatureState );
2037  result.setInlineEncryptionState( inlineEncryptionState );
2038  }
2039 
2040  void ObjectTreeParser::writePartIcon( KMMessagePart * msgPart, int partNum, bool inlineImage ) {
2041  if ( !mReader || !msgPart )
2042  return;
2043 
2044  TQString label = msgPart->fileName();
2045  if( label.isEmpty() )
2046  label = msgPart->name();
2047  if( label.isEmpty() )
2048  label = "unnamed";
2049  label = KMMessage::quoteHtmlChars( label, true );
2050 
2051  TQString comment = msgPart->contentDescription();
2052  comment = KMMessage::quoteHtmlChars( comment, true );
2053  if ( label == comment ) comment = TQString();
2054 
2055  TQString fileName = mReader->writeMessagePartToTempFile( msgPart, partNum );
2056 
2057  TQString href = TQString( "attachment:%1?place=body" ).arg( partNum );
2058 
2059  TQString iconName;
2060  if( inlineImage )
2061  iconName = href;
2062  else {
2063  iconName = msgPart->iconName();
2064  if( iconName.right( 14 ) == "mime_empty.png" ) {
2065  msgPart->magicSetType();
2066  iconName = msgPart->iconName();
2067  }
2068  }
2069 
2070  TQCString contentId = msgPart->contentId();
2071  if ( !contentId.isEmpty() ) {
2072  htmlWriter()->embedPart( contentId, href );
2073  }
2074 
2075  if( inlineImage )
2076  // show the filename of the image below the embedded image
2077  htmlWriter()->queue( "<div><a href=\"" + href + "\">"
2078  "<img src=\"" + fileName + "\" border=\"0\" style=\"max-width: 100%\"></a>"
2079  "</div>"
2080  "<div><a href=\"" + href + "\">" + label + "</a>"
2081  "</div>"
2082  "<div>" + comment + "</div><br>" );
2083  else
2084  // show the filename next to the icon
2085  htmlWriter()->queue( "<div><a href=\"" + href + "\"><img src=\"" +
2086  iconName + "\" border=\"0\" style=\"max-width: 100%\">" + label +
2087  "</a></div>"
2088  "<div>" + comment + "</div><br>" );
2089  }
2090 
2091 #define SIG_FRAME_COL_UNDEF 99
2092 #define SIG_FRAME_COL_RED -1
2093 #define SIG_FRAME_COL_YELLOW 0
2094 #define SIG_FRAME_COL_GREEN 1
2095 TQString ObjectTreeParser::sigStatusToString( const Kleo::CryptoBackend::Protocol* cryptProto,
2096  int status_code,
2097  GpgME::Signature::Summary summary,
2098  int& frameColor,
2099  bool& showKeyInfos )
2100 {
2101  // note: At the moment frameColor and showKeyInfos are
2102  // used for CMS only but not for PGP signatures
2103  // pending(khz): Implement usage of these for PGP sigs as well.
2104  showKeyInfos = true;
2105  TQString result;
2106  if( cryptProto ) {
2107  if( cryptProto == Kleo::CryptoBackendFactory::instance()->openpgp() ) {
2108  // process enum according to it's definition to be read in
2109  // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
2110  switch( status_code ) {
2111  case 0: // GPGME_SIG_STAT_NONE
2112  result = i18n("Error: Signature not verified");
2113  break;
2114  case 1: // GPGME_SIG_STAT_GOOD
2115  result = i18n("Good signature");
2116  break;
2117  case 2: // GPGME_SIG_STAT_BAD
2118  result = i18n("<b>Bad</b> signature");
2119  break;
2120  case 3: // GPGME_SIG_STAT_NOKEY
2121  result = i18n("No public key to verify the signature");
2122  break;
2123  case 4: // GPGME_SIG_STAT_NOSIG
2124  result = i18n("No signature found");
2125  break;
2126  case 5: // GPGME_SIG_STAT_ERROR
2127  result = i18n("Error verifying the signature");
2128  break;
2129  case 6: // GPGME_SIG_STAT_DIFF
2130  result = i18n("Different results for signatures");
2131  break;
2132  /* PENDING(khz) Verify exact meaning of the following values:
2133  case 7: // GPGME_SIG_STAT_GOOD_EXP
2134  return i18n("Signature certificate is expired");
2135  break;
2136  case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
2137  return i18n("One of the certificate's keys is expired");
2138  break;
2139  */
2140  default:
2141  result = ""; // do *not* return a default text here !
2142  break;
2143  }
2144  }
2145  else if ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() ) {
2146  // process status bits according to SigStatus_...
2147  // definitions in tdenetwork/libtdenetwork/cryptplug.h
2148 
2149  if( summary == GpgME::Signature::None ) {
2150  result = i18n("No status information available.");
2151  frameColor = SIG_FRAME_COL_YELLOW;
2152  showKeyInfos = false;
2153  return result;
2154  }
2155 
2156  if( summary & GpgME::Signature::Valid ) {
2157  result = i18n("Good signature.");
2158  // Note:
2159  // Here we are work differently than KMail did before!
2160  //
2161  // The GOOD case ( == sig matching and the complete
2162  // certificate chain was verified and is valid today )
2163  // by definition does *not* show any key
2164  // information but just states that things are OK.
2165  // (khz, according to LinuxTag 2002 meeting)
2166  frameColor = SIG_FRAME_COL_GREEN;
2167  showKeyInfos = false;
2168  return result;
2169  }
2170 
2171  // we are still there? OK, let's test the different cases:
2172 
2173  // we assume green, test for yellow or red (in this order!)
2174  frameColor = SIG_FRAME_COL_GREEN;
2175  TQString result2;
2176  if( summary & GpgME::Signature::KeyExpired ){
2177  // still is green!
2178  result2 += i18n("One key has expired.");
2179  }
2180  if( summary & GpgME::Signature::SigExpired ){
2181  // and still is green!
2182  result2 += i18n("The signature has expired.");
2183  }
2184 
2185  // test for yellow:
2186  if( summary & GpgME::Signature::KeyMissing ) {
2187  result2 += i18n("Unable to verify: key missing.");
2188  // if the signature certificate is missing
2189  // we cannot show infos on it
2190  showKeyInfos = false;
2191  frameColor = SIG_FRAME_COL_YELLOW;
2192  }
2193  if( summary & GpgME::Signature::CrlMissing ){
2194  result2 += i18n("CRL not available.");
2195  frameColor = SIG_FRAME_COL_YELLOW;
2196  }
2197  if( summary & GpgME::Signature::CrlTooOld ){
2198  result2 += i18n("Available CRL is too old.");
2199  frameColor = SIG_FRAME_COL_YELLOW;
2200  }
2201  if( summary & GpgME::Signature::BadPolicy ){
2202  result2 += i18n("A policy was not met.");
2203  frameColor = SIG_FRAME_COL_YELLOW;
2204  }
2205  if( summary & GpgME::Signature::SysError ){
2206  result2 += i18n("A system error occurred.");
2207  // if a system error occurred
2208  // we cannot trust any information
2209  // that was given back by the plug-in
2210  showKeyInfos = false;
2211  frameColor = SIG_FRAME_COL_YELLOW;
2212  }
2213 
2214  // test for red:
2215  if( summary & GpgME::Signature::KeyRevoked ){
2216  // this is red!
2217  result2 += i18n("One key has been revoked.");
2218  frameColor = SIG_FRAME_COL_RED;
2219  }
2220  if( summary & GpgME::Signature::Red ) {
2221  if( result2.isEmpty() )
2222  // Note:
2223  // Here we are work differently than KMail did before!
2224  //
2225  // The BAD case ( == sig *not* matching )
2226  // by definition does *not* show any key
2227  // information but just states that things are BAD.
2228  //
2229  // The reason for this: In this case ALL information
2230  // might be falsificated, we can NOT trust the data
2231  // in the body NOT the signature - so we don't show
2232  // any key/signature information at all!
2233  // (khz, according to LinuxTag 2002 meeting)
2234  showKeyInfos = false;
2235  frameColor = SIG_FRAME_COL_RED;
2236  }
2237  else
2238  result = "";
2239 
2240  if( SIG_FRAME_COL_GREEN == frameColor ) {
2241  result = i18n("Good signature.");
2242  } else if( SIG_FRAME_COL_RED == frameColor ) {
2243  result = i18n("<b>Bad</b> signature.");
2244  } else
2245  result = "";
2246 
2247  if( !result2.isEmpty() ) {
2248  if( !result.isEmpty() )
2249  result.append("<br />");
2250  result.append( result2 );
2251  }
2252  }
2253  /*
2254  // add i18n support for 3rd party plug-ins here:
2255  else if (0 <= cryptPlug->libName().find( "yetanotherpluginname", 0, false )) {
2256 
2257  }
2258  */
2259  }
2260  return result;
2261 }
2262 
2263 
2264 static TQString writeSimpleSigstatHeader( const PartMetaData &block )
2265 {
2266  TQString html;
2267  html += "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td>";
2268 
2269  if ( block.signClass == "signErr" ) {
2270  html += i18n( "Invalid signature." );
2271  } else if ( block.signClass == "signOkKeyBad" || block.signClass == "signWarn" ) {
2272  html += i18n( "Not enough information to check signature validity." );
2273  } else if ( block.signClass == "signOkKeyOk" ) {
2274  TQString addr;
2275  if ( !block.signerMailAddresses.isEmpty() )
2276  addr = block.signerMailAddresses.first();
2277  TQString name = addr;
2278  if ( name.isEmpty() )
2279  name = block.signer;
2280  if ( addr.isEmpty() ) {
2281  html += i18n( "Signature is valid." );
2282  } else {
2283  html += i18n( "Signed by <a href=\"mailto:%1\">%2</a>." ).arg( addr, name );
2284  }
2285  } else {
2286  // should not happen
2287  html += i18n( "Unknown signature state" );
2288  }
2289  html += "</td><td align=\"right\">";
2290  html += "<a href=\"kmail:showSignatureDetails\">";
2291  html += i18n( "Show Details" );
2292  html += "</a></td></tr></table>";
2293  return html;
2294 }
2295 
2296 static TQString beginVerboseSigstatHeader()
2297 {
2298  return "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td rowspan=\"2\">";
2299 }
2300 
2301 static TQString makeShowAuditLogLink( const GpgME::Error & err, const TQString & auditLog ) {
2302  if ( const unsigned int code = err.code() ) {
2303  if ( code == GPG_ERR_NOT_IMPLEMENTED ) {
2304  //kdDebug(5006) << "makeShowAuditLogLink: not showing link (not implemented)" << endl;
2305  return TQString();
2306  } else if ( code == GPG_ERR_NO_DATA ) {
2307  //kdDebug(5006) << "makeShowAuditLogLink: not showing link (not available)" << endl;
2308  return i18n("No Audit Log available");
2309  } else {
2310  return i18n("Error Retrieving Audit Log: %1").arg( TQString::fromLocal8Bit( err.asString() ) );
2311  }
2312  }
2313 
2314  if ( !auditLog.isEmpty() ) {
2315  KURL url;
2316  url.setProtocol( "kmail" );
2317  url.setPath( "showAuditLog" );
2318  url.addQueryItem( "log", auditLog );
2319 
2320  return "<a href=\"" + url.htmlURL() + "\">" + i18n("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + "</a>";
2321  }
2322 
2323  return TQString();
2324 }
2325 
2326 static TQString endVerboseSigstatHeader( const PartMetaData & pmd )
2327 {
2328  TQString html;
2329  html += "</td><td align=\"right\" valign=\"top\" nowrap=\"nowrap\">";
2330  html += "<a href=\"kmail:hideSignatureDetails\">";
2331  html += i18n( "Hide Details" );
2332  html += "</a></td></tr>";
2333  html += "<tr><td align=\"right\" valign=\"bottom\" nowrap=\"nowrap\">";
2334  html += makeShowAuditLogLink( pmd.auditLogError, pmd.auditLog );
2335  html += "</td></tr></table>";
2336  return html;
2337 }
2338 
2339 TQString ObjectTreeParser::writeSigstatHeader( PartMetaData & block,
2340  const Kleo::CryptoBackend::Protocol * cryptProto,
2341  const TQString & fromAddress,
2342  partNode *node )
2343 {
2344  const bool isSMIME = cryptProto && ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() );
2345  TQString signer = block.signer;
2346 
2347  TQString htmlStr, simpleHtmlStr;
2348  TQString dir = ( TQApplication::reverseLayout() ? "rtl" : "ltr" );
2349  TQString cellPadding("cellpadding=\"1\"");
2350 
2351  if( block.isEncapsulatedRfc822Message )
2352  {
2353  htmlStr += "<table cellspacing=\"1\" "+cellPadding+" class=\"rfc822\">"
2354  "<tr class=\"rfc822H\"><td dir=\"" + dir + "\">";
2355  if ( node )
2356  htmlStr += "<a href=\"" + node->asHREF( "body" ) + "\">"
2357  + i18n("Encapsulated message") + "</a>";
2358  else
2359  htmlStr += i18n("Encapsulated message");
2360  htmlStr += "</td></tr><tr class=\"rfc822B\"><td>";
2361  }
2362 
2363  if( block.isEncrypted )
2364  {
2365  htmlStr += "<table cellspacing=\"1\" "+cellPadding+" class=\"encr\">"
2366  "<tr class=\"encrH\"><td dir=\"" + dir + "\">";
2367  if ( block.inProgress )
2368  htmlStr += i18n("Please wait while the message is being decrypted...");
2369  else if ( block.isDecryptable )
2370  htmlStr += i18n("Encrypted message");
2371  else {
2372  htmlStr += i18n("Encrypted message (decryption not possible)");
2373  if( !block.errorText.isEmpty() )
2374  htmlStr += "<br />" + i18n("Reason: %1").arg( block.errorText );
2375  }
2376  htmlStr += "</td></tr><tr class=\"encrB\"><td>";
2377  }
2378 
2379  if ( block.isSigned && block.inProgress )
2380  {
2381  block.signClass = "signInProgress";
2382  htmlStr += "<table cellspacing=\"1\" "+cellPadding+" class=\"signInProgress\">"
2383  "<tr class=\"signInProgressH\"><td dir=\"" + dir + "\">";
2384  htmlStr += i18n("Please wait while the signature is being verified...");
2385  htmlStr += "</td></tr><tr class=\"signInProgressB\"><td>";
2386  }
2387  simpleHtmlStr = htmlStr;
2388 
2389  if ( block.isSigned && !block.inProgress ) {
2390  TQStringList& blockAddrs( block.signerMailAddresses );
2391  // note: At the moment frameColor and showKeyInfos are
2392  // used for CMS only but not for PGP signatures
2393  // pending(khz): Implement usage of these for PGP sigs as well.
2394  int frameColor = SIG_FRAME_COL_UNDEF;
2395  bool showKeyInfos;
2396  bool onlyShowKeyURL = false;
2397  bool cannotCheckSignature = true;
2398  TQString statusStr = sigStatusToString( cryptProto,
2399  block.status_code,
2400  block.sigSummary,
2401  frameColor,
2402  showKeyInfos );
2403  // if needed fallback to english status text
2404  // that was reported by the plugin
2405  if( statusStr.isEmpty() )
2406  statusStr = block.status;
2407  if( block.technicalProblem )
2408  frameColor = SIG_FRAME_COL_YELLOW;
2409 
2410  switch( frameColor ){
2411  case SIG_FRAME_COL_RED:
2412  cannotCheckSignature = false;
2413  break;
2414  case SIG_FRAME_COL_YELLOW:
2415  cannotCheckSignature = true;
2416  break;
2417  case SIG_FRAME_COL_GREEN:
2418  cannotCheckSignature = false;
2419  break;
2420  }
2421 
2422  // compose the string for displaying the key ID
2423  // either as URL or not linked (for PGP)
2424  // note: Once we can start PGP key manager programs
2425  // from within KMail we could change this and
2426  // always show the URL. (khz, 2002/06/27)
2427  TQString startKeyHREF;
2428  if( isSMIME )
2429  startKeyHREF =
2430  TQString("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
2431  .arg( cryptProto->displayName(),
2432  cryptProto->name(),
2433  block.keyId );
2434  TQString keyWithWithoutURL
2435  = isSMIME
2436  ? TQString("%1%2</a>")
2437  .arg( startKeyHREF,
2438  cannotCheckSignature ? i18n("[Details]") : ("0x" + block.keyId) )
2439  : "0x" + TQString::fromUtf8( block.keyId );
2440 
2441 
2442  // temporary hack: always show key infos!
2443  showKeyInfos = true;
2444 
2445  // Sorry for using 'black' as null color but .isValid()
2446  // checking with TQColor default c'tor did not work for
2447  // some reason.
2448  if( isSMIME && (SIG_FRAME_COL_UNDEF != frameColor) ) {
2449 
2450  // new frame settings for CMS:
2451  // beautify the status string
2452  if( !statusStr.isEmpty() ) {
2453  statusStr.prepend("<i>");
2454  statusStr.append( "</i>");
2455  }
2456 
2457  // special color handling: S/MIME uses only green/yellow/red.
2458  switch( frameColor ) {
2459  case SIG_FRAME_COL_RED:
2460  block.signClass = "signErr";//"signCMSRed";
2461  onlyShowKeyURL = true;
2462  break;
2463  case SIG_FRAME_COL_YELLOW:
2464  if( block.technicalProblem )
2465  block.signClass = "signWarn";
2466  else
2467  block.signClass = "signOkKeyBad";//"signCMSYellow";
2468  break;
2469  case SIG_FRAME_COL_GREEN:
2470  block.signClass = "signOkKeyOk";//"signCMSGreen";
2471  // extra hint for green case
2472  // that email addresses in DN do not match fromAddress
2473  TQString greenCaseWarning;
2474  TQString msgFrom( KPIM::getEmailAddress(fromAddress) );
2475  TQString certificate;
2476  if( block.keyId.isEmpty() )
2477  certificate = i18n("certificate");
2478  else
2479  certificate = startKeyHREF + i18n("certificate") + "</a>";
2480  if( !blockAddrs.empty() ){
2481  if( blockAddrs.grep(
2482  msgFrom,
2483  false ).isEmpty() ) {
2484  greenCaseWarning =
2485  "<u>" +
2486  i18n("Warning:") +
2487  "</u> " +
2488  i18n("Sender's mail address is not stored "
2489  "in the %1 used for signing.").arg(certificate) +
2490  "<br />" +
2491  i18n("sender: ") +
2492  msgFrom +
2493  "<br />" +
2494  i18n("stored: ");
2495  // We cannot use TQt's join() function here but
2496  // have to join the addresses manually to
2497  // extract the mail addresses (without '<''>')
2498  // before including it into our string:
2499  bool bStart = true;
2500  for(TQStringList::ConstIterator it = blockAddrs.begin();
2501  it != blockAddrs.end(); ++it ){
2502  if( !bStart )
2503  greenCaseWarning.append(", <br />&nbsp; &nbsp;");
2504  bStart = false;
2505  greenCaseWarning.append( KPIM::getEmailAddress(*it) );
2506  }
2507  }
2508  } else {
2509  greenCaseWarning =
2510  "<u>" +
2511  i18n("Warning:") +
2512  "</u> " +
2513  i18n("No mail address is stored in the %1 used for signing, "
2514  "so we cannot compare it to the sender's address %2.")
2515  .arg(certificate,msgFrom);
2516  }
2517  if( !greenCaseWarning.isEmpty() ) {
2518  if( !statusStr.isEmpty() )
2519  statusStr.append("<br />&nbsp;<br />");
2520  statusStr.append( greenCaseWarning );
2521  }
2522  break;
2523  }
2524 
2525  TQString frame = "<table cellspacing=\"1\" "+cellPadding+" "
2526  "class=\"" + block.signClass + "\">"
2527  "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
2528  htmlStr += frame + beginVerboseSigstatHeader();
2529  simpleHtmlStr += frame;
2530  simpleHtmlStr += writeSimpleSigstatHeader( block );
2531  if( block.technicalProblem ) {
2532  htmlStr += block.errorText;
2533  }
2534  else if( showKeyInfos ) {
2535  if( cannotCheckSignature ) {
2536  htmlStr += i18n( "Not enough information to check "
2537  "signature. %1" )
2538  .arg( keyWithWithoutURL );
2539  }
2540  else {
2541 
2542  if (block.signer.isEmpty())
2543  signer = "";
2544  else {
2545  if( !blockAddrs.empty() ){
2546  TQString address = KMMessage::encodeMailtoUrl( blockAddrs.first() );
2547  signer = "<a href=\"mailto:" + address + "\">" + signer + "</a>";
2548  }
2549  }
2550 
2551  if( block.keyId.isEmpty() ) {
2552  if( signer.isEmpty() || onlyShowKeyURL )
2553  htmlStr += i18n( "Message was signed with unknown key." );
2554  else
2555  htmlStr += i18n( "Message was signed by %1." )
2556  .arg( signer );
2557  } else {
2558  TQDateTime created = block.creationTime;
2559  if( created.isValid() ) {
2560  if( signer.isEmpty() ) {
2561  if( onlyShowKeyURL )
2562  htmlStr += i18n( "Message was signed with key %1." )
2563  .arg( keyWithWithoutURL );
2564  else
2565  htmlStr += i18n( "Message was signed on %1 with key %2." )
2566  .arg( TDEGlobal::locale()->formatDateTime( created ),
2567  keyWithWithoutURL );
2568  }
2569  else {
2570  if( onlyShowKeyURL )
2571  htmlStr += i18n( "Message was signed with key %1." )
2572  .arg( keyWithWithoutURL );
2573  else
2574  htmlStr += i18n( "Message was signed by %3 on %1 with key %2" )
2575  .arg( TDEGlobal::locale()->formatDateTime( created ),
2576  keyWithWithoutURL,
2577  signer );
2578  }
2579  }
2580  else {
2581  if( signer.isEmpty() || onlyShowKeyURL )
2582  htmlStr += i18n( "Message was signed with key %1." )
2583  .arg( keyWithWithoutURL );
2584  else
2585  htmlStr += i18n( "Message was signed by %2 with key %1." )
2586  .arg( keyWithWithoutURL,
2587  signer );
2588  }
2589  }
2590  }
2591  htmlStr += "<br />";
2592  if( !statusStr.isEmpty() ) {
2593  htmlStr += "&nbsp;<br />";
2594  htmlStr += i18n( "Status: " );
2595  htmlStr += statusStr;
2596  }
2597  } else {
2598  htmlStr += statusStr;
2599  }
2600  frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
2601  htmlStr += endVerboseSigstatHeader( block ) + frame;
2602  simpleHtmlStr += frame;
2603 
2604  } else {
2605 
2606  // old frame settings for PGP:
2607 
2608  if( block.signer.isEmpty() || block.technicalProblem ) {
2609  block.signClass = "signWarn";
2610  TQString frame = "<table cellspacing=\"1\" "+cellPadding+" "
2611  "class=\"" + block.signClass + "\">"
2612  "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
2613  htmlStr += frame + beginVerboseSigstatHeader();
2614  simpleHtmlStr += frame;
2615  simpleHtmlStr += writeSimpleSigstatHeader( block );
2616  if( block.technicalProblem ) {
2617  htmlStr += block.errorText;
2618  }
2619  else {
2620  if( !block.keyId.isEmpty() ) {
2621  TQDateTime created = block.creationTime;
2622  if ( created.isValid() )
2623  htmlStr += i18n( "Message was signed on %1 with unknown key %2." )
2624  .arg( TDEGlobal::locale()->formatDateTime( created ),
2625  keyWithWithoutURL );
2626  else
2627  htmlStr += i18n( "Message was signed with unknown key %1." )
2628  .arg( keyWithWithoutURL );
2629  }
2630  else
2631  htmlStr += i18n( "Message was signed with unknown key." );
2632  htmlStr += "<br />";
2633  htmlStr += i18n( "The validity of the signature cannot be "
2634  "verified." );
2635  if( !statusStr.isEmpty() ) {
2636  htmlStr += "<br />";
2637  htmlStr += i18n( "Status: " );
2638  htmlStr += "<i>";
2639  htmlStr += statusStr;
2640  htmlStr += "</i>";
2641  }
2642  }
2643  frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
2644  htmlStr += endVerboseSigstatHeader( block ) + frame;
2645  simpleHtmlStr += frame;
2646  }
2647  else
2648  {
2649  // HTMLize the signer's user id and create mailto: link
2650  signer = KMMessage::quoteHtmlChars( signer, true );
2651  signer = "<a href=\"mailto:" + signer + "\">" + signer + "</a>";
2652 
2653  if (block.isGoodSignature) {
2654  if( block.keyTrust < Kpgp::KPGP_VALIDITY_MARGINAL )
2655  block.signClass = "signOkKeyBad";
2656  else
2657  block.signClass = "signOkKeyOk";
2658  TQString frame = "<table cellspacing=\"1\" "+cellPadding+" "
2659  "class=\"" + block.signClass + "\">"
2660  "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
2661  htmlStr += frame + beginVerboseSigstatHeader();
2662  simpleHtmlStr += frame;
2663  simpleHtmlStr += writeSimpleSigstatHeader( block );
2664  if( !block.keyId.isEmpty() )
2665  htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." )
2666  .arg( keyWithWithoutURL,
2667  signer );
2668  else
2669  htmlStr += i18n( "Message was signed by %1." ).arg( signer );
2670  htmlStr += "<br />";
2671 
2672  switch( block.keyTrust )
2673  {
2674  case Kpgp::KPGP_VALIDITY_UNKNOWN:
2675  htmlStr += i18n( "The signature is valid, but the key's "
2676  "validity is unknown." );
2677  break;
2678  case Kpgp::KPGP_VALIDITY_MARGINAL:
2679  htmlStr += i18n( "The signature is valid and the key is "
2680  "marginally trusted." );
2681  break;
2682  case Kpgp::KPGP_VALIDITY_FULL:
2683  htmlStr += i18n( "The signature is valid and the key is "
2684  "fully trusted." );
2685  break;
2686  case Kpgp::KPGP_VALIDITY_ULTIMATE:
2687  htmlStr += i18n( "The signature is valid and the key is "
2688  "ultimately trusted." );
2689  break;
2690  default:
2691  htmlStr += i18n( "The signature is valid, but the key is "
2692  "untrusted." );
2693  }
2694  frame = "</td></tr>"
2695  "<tr class=\"" + block.signClass + "B\"><td>";
2696  htmlStr += endVerboseSigstatHeader( block ) + frame;
2697  simpleHtmlStr += frame;
2698  }
2699  else
2700  {
2701  block.signClass = "signErr";
2702  TQString frame = "<table cellspacing=\"1\" "+cellPadding+" "
2703  "class=\"" + block.signClass + "\">"
2704  "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
2705  htmlStr += frame + beginVerboseSigstatHeader();
2706  simpleHtmlStr += frame;
2707  simpleHtmlStr += writeSimpleSigstatHeader( block );
2708  if( !block.keyId.isEmpty() )
2709  htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." )
2710  .arg( keyWithWithoutURL,
2711  signer );
2712  else
2713  htmlStr += i18n( "Message was signed by %1." ).arg( signer );
2714  htmlStr += "<br />";
2715  htmlStr += i18n("Warning: The signature is bad.");
2716  frame = "</td></tr>"
2717  "<tr class=\"" + block.signClass + "B\"><td>";
2718  htmlStr += endVerboseSigstatHeader( block ) + frame;
2719  simpleHtmlStr += frame;
2720  }
2721  }
2722  }
2723  }
2724 
2725  if ( mReader->showSignatureDetails() )
2726  return htmlStr;
2727  return simpleHtmlStr;
2728 }
2729 
2730 TQString ObjectTreeParser::writeSigstatFooter( PartMetaData& block )
2731 {
2732  TQString dir = ( TQApplication::reverseLayout() ? "rtl" : "ltr" );
2733 
2734  TQString htmlStr;
2735 
2736  if (block.isSigned) {
2737  htmlStr += "</td></tr><tr class=\"" + block.signClass + "H\">";
2738  htmlStr += "<td dir=\"" + dir + "\">" +
2739  i18n( "End of signed message" ) +
2740  "</td></tr></table>";
2741  }
2742 
2743  if (block.isEncrypted) {
2744  htmlStr += "</td></tr><tr class=\"encrH\"><td dir=\"" + dir + "\">" +
2745  i18n( "End of encrypted message" ) +
2746  "</td></tr></table>";
2747  }
2748 
2749  if( block.isEncapsulatedRfc822Message )
2750  {
2751  htmlStr += "</td></tr><tr class=\"rfc822H\"><td dir=\"" + dir + "\">" +
2752  i18n( "End of encapsulated message" ) +
2753  "</td></tr></table>";
2754  }
2755 
2756  return htmlStr;
2757 }
2758 
2759 //-----------------------------------------------------------------------------
2760 
2761 void ObjectTreeParser::writeAttachmentMarkHeader( partNode *node )
2762 {
2763  if ( !mReader )
2764  return;
2765 
2766  htmlWriter()->queue( TQString( "<div id=\"attachmentDiv%1\">\n" ).arg( node->nodeId() ) );
2767 }
2768 
2769 //-----------------------------------------------------------------------------
2770 
2771 void ObjectTreeParser::writeAttachmentMarkFooter()
2772 {
2773  if ( !mReader )
2774  return;
2775 
2776  htmlWriter()->queue( TQString( "</div>" ) );
2777 }
2778 
2779 //-----------------------------------------------------------------------------
2780 void ObjectTreeParser::writeBodyStr( const TQCString& aStr, const TQTextCodec *aCodec,
2781  const TQString& fromAddress )
2782 {
2783  KMMsgSignatureState dummy1;
2784  KMMsgEncryptionState dummy2;
2785  writeBodyStr( aStr, aCodec, fromAddress, dummy1, dummy2, false );
2786 }
2787 
2788 //-----------------------------------------------------------------------------
2789 void ObjectTreeParser::writeBodyStr( const TQCString& aStr, const TQTextCodec *aCodec,
2790  const TQString& fromAddress,
2791  KMMsgSignatureState& inlineSignatureState,
2792  KMMsgEncryptionState& inlineEncryptionState,
2793  bool decorate )
2794 {
2795  bool goodSignature = false;
2796  Kpgp::Module* pgp = Kpgp::Module::getKpgp();
2797  assert(pgp != 0);
2798  bool isPgpMessage = false; // true if the message contains at least one
2799  // PGP MESSAGE or one PGP SIGNED MESSAGE block
2800  TQString dir = ( TQApplication::reverseLayout() ? "rtl" : "ltr" );
2801  TQString headerStr = TQString("<div dir=\"%1\">").arg(dir);
2802 
2803  inlineSignatureState = KMMsgNotSigned;
2804  inlineEncryptionState = KMMsgNotEncrypted;
2805  TQPtrList<Kpgp::Block> pgpBlocks;
2806  TQStrList nonPgpBlocks;
2807  if( Kpgp::Module::prepareMessageForDecryption( aStr, pgpBlocks, nonPgpBlocks ) )
2808  {
2809  bool isEncrypted = false, isSigned = false;
2810  bool fullySignedOrEncrypted = true;
2811  bool firstNonPgpBlock = true;
2812  bool couldDecrypt = false;
2813  TQString signer;
2814  TQCString keyId;
2815  TQString decryptionError;
2816  Kpgp::Validity keyTrust = Kpgp::KPGP_VALIDITY_FULL;
2817 
2818  TQPtrListIterator<Kpgp::Block> pbit( pgpBlocks );
2819 
2820  TQStrListIterator npbit( nonPgpBlocks );
2821 
2822  TQString htmlStr;
2823  for( ; *pbit != 0; ++pbit, ++npbit )
2824  {
2825  // insert the next Non-OpenPGP block
2826  TQCString str( *npbit );
2827  if( !str.isEmpty() ) {
2828  htmlStr += quotedHTML( aCodec->toUnicode( str ), decorate );
2829  //kdDebug( 5006 ) << "Non-empty Non-OpenPGP block found: '" << str
2830  // << "'" << endl;
2831  // treat messages with empty lines before the first clearsigned
2832  // block as fully signed/encrypted
2833  if( firstNonPgpBlock ) {
2834  // check whether str only consists of \n
2835  for( TQCString::ConstIterator c = str.begin(); *c; ++c ) {
2836  if( *c != '\n' ) {
2837  fullySignedOrEncrypted = false;
2838  break;
2839  }
2840  }
2841  }
2842  else {
2843  fullySignedOrEncrypted = false;
2844  }
2845  }
2846  firstNonPgpBlock = false;
2847 
2848  //htmlStr += "<br>";
2849 
2850  Kpgp::Block* block = *pbit;
2851  if( ( block->type() == Kpgp::PgpMessageBlock &&
2852  // ### Workaround for bug 56693
2853  !kmkernel->contextMenuShown() ) ||
2854  ( block->type() == Kpgp::ClearsignedBlock ) )
2855  {
2856  isPgpMessage = true;
2857  if( block->type() == Kpgp::PgpMessageBlock )
2858  {
2859  if ( mReader )
2860  emit mReader->noDrag();
2861  // try to decrypt this OpenPGP block
2862  couldDecrypt = block->decrypt();
2863  isEncrypted = block->isEncrypted();
2864  if (!couldDecrypt) {
2865  decryptionError = pgp->lastErrorMsg();
2866  }
2867  }
2868  else
2869  {
2870  // try to verify this OpenPGP block
2871  block->verify();
2872  }
2873 
2874  isSigned = block->isSigned();
2875  if( isSigned )
2876  {
2877  keyId = block->signatureKeyId();
2878  signer = block->signatureUserId();
2879  if( !signer.isEmpty() )
2880  {
2881  goodSignature = block->goodSignature();
2882 
2883  if( !keyId.isEmpty() ) {
2884  keyTrust = pgp->keyTrust( keyId );
2885  Kpgp::Key* key = pgp->publicKey( keyId );
2886  if ( key ) {
2887  // Use the user ID from the key because this one
2888  // is charset safe.
2889  signer = key->primaryUserID();
2890  }
2891  }
2892  else
2893  // This is needed for the PGP 6 support because PGP 6 doesn't
2894  // print the key id of the signing key if the key is known.
2895  keyTrust = pgp->keyTrust( signer );
2896  }
2897  }
2898 
2899  if( isSigned )
2900  inlineSignatureState = KMMsgPartiallySigned;
2901  if( isEncrypted )
2902  inlineEncryptionState = KMMsgPartiallyEncrypted;
2903 
2904  PartMetaData messagePart;
2905 
2906  messagePart.isSigned = isSigned;
2907  messagePart.technicalProblem = false;
2908  messagePart.isGoodSignature = goodSignature;
2909  messagePart.isEncrypted = isEncrypted;
2910  messagePart.isDecryptable = couldDecrypt;
2911  messagePart.decryptionError = decryptionError;
2912  messagePart.signer = signer;
2913  messagePart.keyId = keyId;
2914  messagePart.keyTrust = keyTrust;
2915 
2916  htmlStr += writeSigstatHeader( messagePart, 0, fromAddress );
2917 
2918  htmlStr += quotedHTML( aCodec->toUnicode( block->text() ), decorate );
2919  htmlStr += writeSigstatFooter( messagePart );
2920  }
2921  else // block is neither message block nor clearsigned block
2922  htmlStr += quotedHTML( aCodec->toUnicode( block->text() ),
2923  decorate );
2924  }
2925 
2926  // add the last Non-OpenPGP block
2927  TQCString str( nonPgpBlocks.last() );
2928  if( !str.isEmpty() ) {
2929  htmlStr += quotedHTML( aCodec->toUnicode( str ), decorate );
2930  // Even if the trailing Non-OpenPGP block isn't empty we still
2931  // consider the message part fully signed/encrypted because else
2932  // all inline signed mailing list messages would only be partially
2933  // signed because of the footer which is often added by the mailing
2934  // list software. IK, 2003-02-15
2935  }
2936  if( fullySignedOrEncrypted ) {
2937  if( inlineSignatureState == KMMsgPartiallySigned )
2938  inlineSignatureState = KMMsgFullySigned;
2939  if( inlineEncryptionState == KMMsgPartiallyEncrypted )
2940  inlineEncryptionState = KMMsgFullyEncrypted;
2941  }
2942  htmlWriter()->queue( htmlStr );
2943  }
2944  else
2945  htmlWriter()->queue( quotedHTML( aCodec->toUnicode( aStr ), decorate ) );
2946 }
2947 
2948 
2949 TQString ObjectTreeParser::quotedHTML( const TQString& s, bool decorate )
2950 {
2951  assert( mReader );
2952  assert( cssHelper() );
2953 
2954  int convertFlags = LinkLocator::PreserveSpaces;
2955  if ( decorate && GlobalSettings::self()->showEmoticons() ) {
2956  convertFlags |= LinkLocator::ReplaceSmileys;
2957  }
2958  TQString htmlStr;
2959  const TQString normalStartTag = cssHelper()->nonQuotedFontTag();
2960  TQString quoteFontTag[3];
2961  TQString deepQuoteFontTag[3];
2962  for ( int i = 0 ; i < 3 ; ++i ) {
2963  quoteFontTag[i] = cssHelper()->quoteFontTag( i );
2964  deepQuoteFontTag[i] = cssHelper()->quoteFontTag( i+3 );
2965  }
2966  const TQString normalEndTag = "</div>";
2967  const TQString quoteEnd = "</div>";
2968 
2969  unsigned int pos, beg;
2970  const unsigned int length = s.length();
2971 
2972  // skip leading empty lines
2973  for ( pos = 0; pos < length && s.at(pos) <= TQChar(' '); pos++ ) { ; }
2974  while (pos > 0 && (s[pos-1] == ' ' || s[pos-1] == '\t')) pos--;
2975  beg = pos;
2976 
2977  int currQuoteLevel = -2; // -2 == no previous lines
2978  bool curHidden = false; // no hide any block
2979 
2980  while (beg<length)
2981  {
2982  TQString line;
2983 
2984  /* search next occurrence of '\n' */
2985  pos = s.find('\n', beg, FALSE);
2986  if (pos == (unsigned int)(-1))
2987  pos = length;
2988 
2989  line = s.mid(beg,pos-beg);
2990  beg = pos+1;
2991 
2992  /* calculate line's current quoting depth */
2993  int actQuoteLevel = -1;
2994 
2995  if ( GlobalSettings::self()->showExpandQuotesMark() )
2996  {
2997  // Cache Icons
2998  if ( mCollapseIcon.isEmpty() ) {
2999  mCollapseIcon= LinkLocator::pngToDataUrl(
3000  TDEGlobal::instance()->iconLoader()->iconPath( "quotecollapse",0 ));
3001  }
3002  if ( mExpandIcon.isEmpty() )
3003  mExpandIcon= LinkLocator::pngToDataUrl(
3004  TDEGlobal::instance()->iconLoader()->iconPath( "quoteexpand",0 ));
3005  }
3006 
3007  for (unsigned int p=0; p<line.length(); p++) {
3008  switch (line[p].latin1()) {
3009  case '>':
3010  case '|':
3011  actQuoteLevel++;
3012  break;
3013  case ' ': // spaces and tabs are allowed between the quote markers
3014  case '\t':
3015  case '\r':
3016  break;
3017  default: // stop quoting depth calculation
3018  p = line.length();
3019  break;
3020  }
3021  } /* for() */
3022 
3023  bool actHidden = false;
3024  TQString textExpand;
3025 
3026  // This quoted line needs be hiden
3027  if (GlobalSettings::self()->showExpandQuotesMark() && mReader->mLevelQuote >= 0
3028  && mReader->mLevelQuote <= ( actQuoteLevel ) )
3029  actHidden = true;
3030 
3031  if ( actQuoteLevel != currQuoteLevel ) {
3032  /* finish last quotelevel */
3033  if (currQuoteLevel == -1)
3034  htmlStr.append( normalEndTag );
3035  else if ( currQuoteLevel >= 0 && !curHidden )
3036  htmlStr.append( quoteEnd );
3037 
3038  /* start new quotelevel */
3039  if (actQuoteLevel == -1)
3040  htmlStr += normalStartTag;
3041  else
3042  {
3043  if ( GlobalSettings::self()->showExpandQuotesMark() )
3044  {
3045  if ( actHidden )
3046  {
3047  //only show the QuoteMark when is the first line of the level hidden
3048  if ( !curHidden )
3049  {
3050  //Expand all quotes
3051  htmlStr += "<div class=\"quotelevelmark\" >" ;
3052  htmlStr += TQString( "<a href=\"kmail:levelquote?%1 \">"
3053  "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
3054  .arg(-1)
3055  .arg( mExpandIcon );
3056  htmlStr += "</div><br/>";
3057  htmlStr += quoteEnd;
3058  }
3059  }else {
3060  htmlStr += "<div class=\"quotelevelmark\" >" ;
3061  htmlStr += TQString( "<a href=\"kmail:levelquote?%1 \">"
3062  "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
3063  .arg(actQuoteLevel)
3064  .arg( mCollapseIcon);
3065  htmlStr += "</div>";
3066  if ( actQuoteLevel < 3 )
3067  htmlStr += quoteFontTag[actQuoteLevel];
3068  else
3069  htmlStr += deepQuoteFontTag[actQuoteLevel%3];
3070  }
3071  } else
3072  if ( actQuoteLevel < 3 )
3073  htmlStr += quoteFontTag[actQuoteLevel];
3074  else
3075  htmlStr += deepQuoteFontTag[actQuoteLevel%3];
3076  }
3077  currQuoteLevel = actQuoteLevel;
3078  }
3079  curHidden = actHidden;
3080 
3081 
3082  if ( !actHidden )
3083  {
3084  // don't write empty <div ...></div> blocks (they have zero height)
3085  // ignore ^M DOS linebreaks
3086  if( !line.replace('\015', "").isEmpty() )
3087  {
3088  htmlStr +=TQString( "<div dir=\"%1\">" ).arg( line.isRightToLeft() ? "rtl":"ltr" );
3089  htmlStr += LinkLocator::convertToHtml( line, convertFlags );
3090  htmlStr += TQString( "</div>" );
3091  }
3092  else
3093  htmlStr += "<br>";
3094  }
3095  } /* while() */
3096 
3097  /* really finish the last quotelevel */
3098  if (currQuoteLevel == -1)
3099  htmlStr.append( normalEndTag );
3100  else
3101  htmlStr.append( quoteEnd );
3102 
3103  return htmlStr;
3104 }
3105 
3106 
3107 
3108  const TQTextCodec * ObjectTreeParser::codecFor( partNode * node ) const {
3109  assert( node );
3110  if ( mReader && mReader->overrideCodec() )
3111  return mReader->overrideCodec();
3112  return node->msgPart().codec();
3113  }
3114 
3115 #ifdef MARCS_DEBUG
3116  void ObjectTreeParser::dumpToFile( const char * filename, const char * start,
3117  size_t len ) {
3118  assert( filename );
3119 
3120  TQFile f( filename );
3121  if ( f.open( IO_WriteOnly ) ) {
3122  if ( start ) {
3123  TQDataStream ds( &f );
3124  ds.writeRawBytes( start, len );
3125  }
3126  f.close(); // If data is 0 we just create a zero length file.
3127  }
3128  }
3129 #endif // !NDEBUG
3130 
3131 
3132 } // namespace KMail
This is a Mime Message.
Definition: kmmessage.h:68
static TQString quoteHtmlChars(const TQString &str, bool removeLineBreaks=false)
Quotes the following characters which have a special meaning in HTML: '<' '>' '&' '"'....
Definition: kmmessage.cpp:3804
static TQCString html2source(const TQCString &src)
Convert '<' into "<" resp.
Definition: kmmessage.cpp:3389
static TQString encodeMailtoUrl(const TQString &str)
Encodes an email address as mailto URL.
Definition: kmmessage.cpp:3462
This class implements a "reader window", that is a window used for reading or viewing messages.
Definition: kmreaderwin.h:75
KMail::HtmlWriter * htmlWriter()
Return the HtmlWriter connected to the TDEHTMLPart we use.
Definition: kmreaderwin.h:255
const KMail::AttachmentStrategy * attachmentStrategy() const
Get/set the message attachment strategy.
Definition: kmreaderwin.h:121
TQCString CString(const DwString &str)
Construct a TQCString from a DwString.
Definition: util.cpp:113
folderdiaquotatab.h
Definition: aboutdata.cpp:40