1 /* -*- mode: C++; c-file-style: "gnu" -*-
2 Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
3 Copyright (c) 2009 Andras Mantia <andras@kdab.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include <config-messageviewer.h>
21 #include "nodehelper.h"
22 #include "iconnamecache.h"
23 #include "globalsettings.h"
24 #include "partmetadata.h"
25 #include "interfaces/bodypart.h"
28 #include <messagecore/nodehelper.h>
29 #include <messagecore/stringutil.h>
30 #include "messagecore/globalsettings.h"
32 #include <kmime/kmime_content.h>
33 #include <kmime/kmime_message.h>
34 #include <kmime/kmime_headers.h>
35 #include <kmimetype.h>
38 #include <ktemporaryfile.h>
40 #include <kcharsets.h>
42 #include <kpimutils/kfileio.h>
52 namespace MessageViewer
{
54 QStringList
replySubjPrefixes(QStringList() << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:");
55 QStringList
forwardSubjPrefixes( QStringList() << "Fwd:" << "FW:");
57 NodeHelper::NodeHelper()
59 //TODO(Andras) add methods to modify these prefixes
61 mLocalCodec
= QTextCodec::codecForName( KGlobal::locale()->encoding() );
63 // In the case of Japan. Japanese locale name is "eucjp" but
64 // The Japanese mail systems normally used "iso-2022-jp" of locale name.
65 // We want to change locale name from eucjp to iso-2022-jp at KMail only.
67 // (Introduction to i18n, 6.6 Limit of Locale technology):
68 // EUC-JP is the de-facto standard for UNIX systems, ISO 2022-JP
69 // is the standard for Internet, and Shift-JIS is the encoding
70 // for Windows and Macintosh.
72 if ( mLocalCodec
->name().toLower() == "eucjp"
73 #if defined Q_WS_WIN || defined Q_WS_MACX
74 || mLocalCodec
->name().toLower() == "shift-jis" // OK?
78 mLocalCodec
= QTextCodec::codecForName("jis7");
79 // QTextCodec *cdc = QTextCodec::codecForName("jis7");
80 // QTextCodec::setCodecForLocale(cdc);
81 // KGlobal::locale()->setEncoding(cdc->mibEnum());
86 NodeHelper::~NodeHelper()
90 void NodeHelper::setNodeProcessed(KMime::Content
* node
, bool recurse
)
94 mProcessedNodes
.append( node
);
95 //kDebug() << "Node processed: " << node->index().toString() << node->contentType()->as7BitString();
96 //<< " decodedContent" << node->decodedContent();
98 KMime::Content::List contents
= node
->contents();
99 Q_FOREACH( KMime::Content
*c
, contents
)
101 setNodeProcessed( c
, true );
106 void NodeHelper::setNodeUnprocessed(KMime::Content
* node
, bool recurse
)
110 mProcessedNodes
.removeAll( node
);
112 //avoid double addition of extra nodes, eg. encrypted attachments
113 const QMap
<KMime::Content
*, QList
<KMime::Content
*> >::iterator it
= mExtraContents
.find( node
);
114 if ( it
!= mExtraContents
.end() ) {
115 Q_FOREACH( KMime::Content
* c
, it
.value() ) {
116 KMime::Content
* p
= c
->parent();
118 p
->removeContent( c
);
120 qDeleteAll( it
.value() );
121 //kDebug() << "mExtraContents deleted for" << it.key();
122 mExtraContents
.erase( it
);
125 //kDebug() << "Node UNprocessed: " << node;
127 KMime::Content::List contents
= node
->contents();
128 Q_FOREACH( KMime::Content
*c
, contents
)
130 setNodeUnprocessed( c
, true );
135 bool NodeHelper::nodeProcessed( KMime::Content
* node
) const
139 return mProcessedNodes
.contains( node
);
142 static void clearBodyPartMemento(QMap
<QByteArray
, Interface::BodyPartMemento
*> & bodyPartMementoMap
)
144 for ( QMap
<QByteArray
, Interface::BodyPartMemento
*>::iterator
145 it
= bodyPartMementoMap
.begin(), end
= bodyPartMementoMap
.end();
147 Interface::BodyPartMemento
*memento
= it
.value();
151 bodyPartMementoMap
.clear();
155 void NodeHelper::clear()
157 mProcessedNodes
.clear();
158 mEncryptionState
.clear();
159 mSignatureState
.clear();
160 mOverrideCodecs
.clear();
161 std::for_each( mBodyPartMementoMap
.begin(), mBodyPartMementoMap
.end(),
162 &clearBodyPartMemento
);
163 mBodyPartMementoMap
.clear();
164 QMap
<KMime::Content
*, QList
<KMime::Content
*> >::ConstIterator
end( mExtraContents
.constEnd() );
166 for ( QMap
<KMime::Content
*, QList
<KMime::Content
*> >::ConstIterator it
= mExtraContents
.constBegin(); it
!= end
; ++it
) {
167 Q_FOREACH( KMime::Content
* c
, it
.value() ) {
168 KMime::Content
* p
= c
->parent();
170 p
->removeContent( c
);
172 qDeleteAll( it
.value() );
173 kDebug() << "mExtraContents deleted for" << it
.key();
175 mExtraContents
.clear();
176 mDisplayEmbeddedNodes
.clear();
177 mDisplayHiddenNodes
.clear();
181 void NodeHelper::setEncryptionState( KMime::Content
* node
, const KMMsgEncryptionState state
)
183 mEncryptionState
[node
] = state
;
186 KMMsgEncryptionState
NodeHelper::encryptionState( KMime::Content
*node
) const
188 return mEncryptionState
.value( node
, KMMsgNotEncrypted
);
191 void NodeHelper::setSignatureState( KMime::Content
* node
, const KMMsgSignatureState state
)
193 mSignatureState
[node
] = state
;
196 KMMsgSignatureState
NodeHelper::signatureState( KMime::Content
*node
) const
198 return mSignatureState
.value( node
, KMMsgNotSigned
);
201 PartMetaData
NodeHelper::partMetaData(KMime::Content
* node
)
203 return mPartMetaDatas
.value( node
, PartMetaData() );
206 void NodeHelper::setPartMetaData(KMime::Content
* node
, const PartMetaData
& metaData
)
208 mPartMetaDatas
.insert( node
, metaData
);
211 QString
NodeHelper::writeNodeToTempFile(KMime::Content
* node
)
213 // If the message part is already written to a file, no point in doing it again.
214 // This function is called twice actually, once from the rendering of the attachment
215 // in the body and once for the header.
216 KUrl existingFileName
= tempFileUrlFromNode( node
);
217 if ( !existingFileName
.isEmpty() ) {
218 return existingFileName
.toLocalFile();
221 QString fname
= createTempDir( node
->index().toString() );
222 if ( fname
.isEmpty() )
225 QString fileName
= NodeHelper::fileName( node
);
226 // strip off a leading path
227 int slashPos
= fileName
.lastIndexOf( '/' );
229 fileName
= fileName
.mid( slashPos
+ 1 );
230 if( fileName
.isEmpty() )
231 fileName
= "unnamed";
232 fname
+= '/' + fileName
;
234 //kDebug() << "Create temp file: " << fname;
236 QByteArray data
= node
->decodedContent();
237 if ( node
->contentType()->isText() && data
.size() > 0 ) {
238 // convert CRLF to LF before writing text attachments to disk
239 data
= KMime::CRLFtoLF( data
);
241 if( !KPIMUtils::kByteArrayToFile( data
, fname
, false, false, false ) )
244 mTempFiles
.append( fname
);
245 // make file read-only so that nobody gets the impression that he might
246 // edit attached files (cf. bug #52813)
247 ::chmod( QFile::encodeName( fname
), S_IRUSR
);
254 KUrl
NodeHelper::tempFileUrlFromNode( const KMime::Content
*node
)
259 const QString index
= node
->index().toString();
261 foreach ( const QString
&path
, mTempFiles
) {
262 int right
= path
.lastIndexOf( '/' );
263 int left
= path
.lastIndexOf( ".index.", right
);
267 QStringRef
storedIndex( &path
, left
, right
- left
);
268 if ( left
!= -1 && storedIndex
== index
)
275 QString
NodeHelper::createTempDir( const QString
¶m
)
277 KTemporaryFile
*tempFile
= new KTemporaryFile();
278 tempFile
->setSuffix( ".index." + param
);
280 QString fname
= tempFile
->fileName();
283 if ( ::access( QFile::encodeName( fname
), W_OK
) != 0 ) {
284 // Not there or not writable
285 if( KDE_mkdir( QFile::encodeName( fname
), 0 ) != 0 ||
286 ::chmod( QFile::encodeName( fname
), S_IRWXU
) != 0 ) {
287 return QString(); //failed create
291 Q_ASSERT( !fname
.isNull() );
293 mTempDirs
.append( fname
);
298 void NodeHelper::removeTempFiles()
300 QStringList::ConstIterator end
= mTempFiles
.constEnd();
301 for (QStringList::ConstIterator it
= mTempFiles
.constBegin(); it
!= end
;
307 end
= mTempDirs
.constEnd();
308 for (QStringList::ConstIterator it
= mTempDirs
.constBegin(); it
!= end
;
311 QDir(*it
).rmdir(*it
);
316 void NodeHelper::addTempFile( const QString
& file
)
318 mTempFiles
.append( file
);
321 bool NodeHelper::isToltecMessage( KMime::Content
* node
)
323 if ( !node
->contentType( false ) )
326 if ( node
->contentType()->mediaType().toLower() != "multipart" ||
327 node
->contentType()->subType().toLower() != "mixed" )
330 if ( node
->contents().size() != 3 )
333 const KMime::Headers::Base
*libraryHeader
= node
->headerByType( "X-Library" );
334 if ( !libraryHeader
)
337 if ( QString::fromLatin1( libraryHeader
->as7BitString( false ) ).toLower() !=
338 QLatin1String( "toltec" ) )
341 const KMime::Headers::Base
*kolabTypeHeader
= node
->headerByType( "X-Kolab-Type" );
342 if ( !kolabTypeHeader
)
345 if ( !QString::fromLatin1( kolabTypeHeader
->as7BitString( false ) ).toLower().startsWith(
346 QLatin1String( "application/x-vnd.kolab" ) ) )
352 bool NodeHelper::isInEncapsulatedMessage( KMime::Content
* node
)
354 const KMime::Content
* const topLevel
= node
->topLevel();
355 const KMime::Content
* cur
= node
;
356 while ( cur
&& cur
!= topLevel
) {
357 const bool parentIsMessage
= cur
->parent() && cur
->parent()->contentType( false ) &&
358 cur
->parent()->contentType()->mimeType().toLower() == "message/rfc822";
359 if ( parentIsMessage
&& cur
->parent() != topLevel
) {
368 QByteArray
NodeHelper::charset( KMime::Content
*node
)
370 if ( node
->contentType( false ) )
371 return node
->contentType( false )->charset();
373 return node
->defaultCharset();
376 KMMsgEncryptionState
NodeHelper::overallEncryptionState( KMime::Content
*node
) const
378 KMMsgEncryptionState myState
= KMMsgEncryptionStateUnknown
;
382 if( encryptionState( node
) == KMMsgNotEncrypted
) {
383 // NOTE: children are tested ONLY when parent is not encrypted
384 KMime::Content
*child
= MessageCore::NodeHelper::firstChild( node
);
386 myState
= overallEncryptionState( child
);
388 myState
= KMMsgNotEncrypted
;
390 else { // part is partially or fully encrypted
391 myState
= encryptionState( node
);
393 // siblings are tested always
394 KMime::Content
* next
= MessageCore::NodeHelper::nextSibling( node
);
396 KMMsgEncryptionState otherState
= overallEncryptionState( next
);
397 switch( otherState
) {
398 case KMMsgEncryptionStateUnknown
:
400 case KMMsgNotEncrypted
:
401 if( myState
== KMMsgFullyEncrypted
)
402 myState
= KMMsgPartiallyEncrypted
;
403 else if( myState
!= KMMsgPartiallyEncrypted
)
404 myState
= KMMsgNotEncrypted
;
406 case KMMsgPartiallyEncrypted
:
407 myState
= KMMsgPartiallyEncrypted
;
409 case KMMsgFullyEncrypted
:
410 if( myState
!= KMMsgFullyEncrypted
)
411 myState
= KMMsgPartiallyEncrypted
;
413 case KMMsgEncryptionProblematic
:
418 //kDebug() <<"\n\n KMMsgEncryptionState:" << myState;
424 KMMsgSignatureState
NodeHelper::overallSignatureState( KMime::Content
* node
) const
426 KMMsgSignatureState myState
= KMMsgSignatureStateUnknown
;
430 if( signatureState( node
) == KMMsgNotSigned
) {
431 // children are tested ONLY when parent is not signed
432 KMime::Content
* child
= MessageCore::NodeHelper::firstChild( node
);
434 myState
= overallSignatureState( child
);
436 myState
= KMMsgNotSigned
;
438 else { // part is partially or fully signed
439 myState
= signatureState( node
);
441 // siblings are tested always
442 KMime::Content
*next
= MessageCore::NodeHelper::nextSibling( node
);
444 KMMsgSignatureState otherState
= overallSignatureState( next
);
445 switch( otherState
) {
446 case KMMsgSignatureStateUnknown
:
449 if( myState
== KMMsgFullySigned
)
450 myState
= KMMsgPartiallySigned
;
451 else if( myState
!= KMMsgPartiallySigned
)
452 myState
= KMMsgNotSigned
;
454 case KMMsgPartiallySigned
:
455 myState
= KMMsgPartiallySigned
;
457 case KMMsgFullySigned
:
458 if( myState
!= KMMsgFullySigned
)
459 myState
= KMMsgPartiallySigned
;
461 case KMMsgSignatureProblematic
:
466 //kDebug() <<"\n\n KMMsgSignatureState:" << myState;
471 QString
NodeHelper::iconName( KMime::Content
*node
, int size
)
476 QByteArray mimeType
= node
->contentType()->mimeType();
477 kAsciiToLower( mimeType
.data() );
478 return Util::fileNameForMimetype( mimeType
, size
, node
->contentDisposition()->filename(),
479 node
->contentType()->name() );
482 void NodeHelper::magicSetType( KMime::Content
* node
, bool aAutoDecode
)
484 const QByteArray body
= ( aAutoDecode
) ? node
->decodedContent() : node
->body() ;
485 KMimeType::Ptr mime
= KMimeType::findByContent( body
);
487 QString mimetype
= mime
->name();
488 node
->contentType()->setMimeType( mimetype
.toLatin1() );
492 QString
NodeHelper::replacePrefixes( const QString
& str
,
493 const QStringList
& prefixRegExps
,
495 const QString
& newPrefix
)
497 bool recognized
= false;
498 // construct a big regexp that
499 // 1. is anchored to the beginning of str (sans whitespace)
500 // 2. matches at least one of the part regexps in prefixRegExps
501 QString bigRegExp
= QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*")
502 .arg( prefixRegExps
.join(")|(?:") );
503 QRegExp
rx( bigRegExp
, Qt::CaseInsensitive
);
504 if ( !rx
.isValid() ) {
505 kWarning() << "bigRegExp = \""
506 << bigRegExp
<< "\"\n"
507 << "prefix regexp is invalid!";
508 // try good ole Re/Fwd:
509 recognized
= str
.startsWith( newPrefix
);
512 if ( rx
.indexIn( tmp
) == 0 ) {
515 return tmp
.replace( 0, rx
.matchedLength(), newPrefix
+ ' ' );
519 return newPrefix
+ ' ' + str
;
524 QString
NodeHelper::cleanSubject( KMime::Message
*message
)
526 return cleanSubject( message
, replySubjPrefixes
+ forwardSubjPrefixes
,
527 true, QString() ).trimmed();
530 QString
NodeHelper::cleanSubject( KMime::Message
*message
, const QStringList
& prefixRegExps
,
532 const QString
& newPrefix
)
534 return NodeHelper::replacePrefixes( message
->subject()->asUnicodeString(), prefixRegExps
, replace
,
538 void NodeHelper::setOverrideCodec( KMime::Content
* node
, const QTextCodec
* codec
)
543 mOverrideCodecs
[node
] = codec
;
546 const QTextCodec
* NodeHelper::codec( KMime::Content
* node
)
551 const QTextCodec
*c
= mOverrideCodecs
.value( node
, 0 );
553 // no override-codec set for this message, try the CT charset parameter:
554 c
= codecForName( node
->contentType()->charset() );
557 // Ok, no override and nothing in the message, let's use the fallback
558 // the user configured
559 c
= codecForName( MessageCore::GlobalSettings::self()->fallbackCharacterEncoding().toLatin1() );
562 // no charset means us-ascii (RFC 2045), so using local encoding should
569 const QTextCodec
* NodeHelper::codecForName(const QByteArray
& _str
)
573 QByteArray codec
= _str
;
574 kAsciiToLower(codec
.data());
575 return KGlobal::charsets()->codecForName(codec
);
578 QString
NodeHelper::fileName( const KMime::Content
*node
)
580 QString name
= const_cast<KMime::Content
*>( node
)->contentDisposition()->filename();
581 if ( name
.isEmpty() )
582 name
= const_cast<KMime::Content
*>( node
)->contentType()->name();
584 name
= name
.trimmed();
588 //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalBodyPartMemento replacement
589 Interface::BodyPartMemento
*NodeHelper::bodyPartMemento( KMime::Content
*node
,
590 const QByteArray
&which
) const
592 const QMap
< QString
, QMap
<QByteArray
,Interface::BodyPartMemento
*> >::const_iterator nit
593 = mBodyPartMementoMap
.find( persistentIndex( node
) );
594 if ( nit
== mBodyPartMementoMap
.end() )
596 const QMap
<QByteArray
,Interface::BodyPartMemento
*>::const_iterator it
=
597 nit
->find( which
.toLower() );
598 return it
!= nit
->end() ? it
.value() : 0 ;
601 //FIXME(Andras) review it (by Marc?) to see if I got it right. This is supposed to be the partNode::internalSetBodyPartMemento replacement
602 void NodeHelper::setBodyPartMemento( KMime::Content
* node
, const QByteArray
&which
,
603 Interface::BodyPartMemento
*memento
)
605 QMap
<QByteArray
,Interface::BodyPartMemento
*> & mementos
606 = mBodyPartMementoMap
[persistentIndex(node
)];
608 const QMap
<QByteArray
,Interface::BodyPartMemento
*>::iterator it
=
609 mementos
.lowerBound( which
.toLower() );
611 if ( it
!= mementos
.end() && it
.key() == which
.toLower() ) {
614 it
.value() = memento
;
616 mementos
.erase( it
);
619 mementos
.insert( which
.toLower(), memento
);
623 bool NodeHelper::isNodeDisplayedEmbedded( KMime::Content
* node
) const
625 //kDebug() << "IS NODE: " << mDisplayEmbeddedNodes.contains( node );
626 return mDisplayEmbeddedNodes
.contains( node
);
629 void NodeHelper::setNodeDisplayedEmbedded( KMime::Content
* node
, bool displayedEmbedded
)
631 //kDebug() << "SET NODE: " << node << displayedEmbedded;
632 if ( displayedEmbedded
)
633 mDisplayEmbeddedNodes
.insert( node
);
635 mDisplayEmbeddedNodes
.remove( node
);
638 bool NodeHelper::isNodeDisplayedHidden( KMime::Content
* node
) const
640 return mDisplayHiddenNodes
.contains( node
);
643 void NodeHelper::setNodeDisplayedHidden( KMime::Content
* node
, bool displayedHidden
)
645 if( displayedHidden
) {
646 mDisplayHiddenNodes
.insert( node
);
648 mDisplayEmbeddedNodes
.remove( node
);
653 Creates a persistent index string that bridges the gap between the
654 permanent nodes and the temporary ones.
656 Used internally for robust indexing.
658 QString
NodeHelper::persistentIndex( const KMime::Content
* node
) const
663 QString indexStr
= node
->index().toString();
664 const KMime::Content
* const topLevel
= node
->topLevel();
665 //if the node is an extra node, prepend the index of the extra node to the url
666 Q_FOREACH( const QList
<KMime::Content
*> & extraNodes
, mExtraContents
) {
667 const int extraNodesSize( extraNodes
.size() );
668 for ( int i
= 0; i
< extraNodesSize
; ++i
)
669 if ( topLevel
== extraNodes
[i
] )
670 return indexStr
.prepend( QString::fromLatin1("%1:").arg(i
) );
675 QString
NodeHelper::asHREF( const KMime::Content
* node
, const QString
&place
)
677 return QString::fromLatin1( "attachment:%1?place=%2" ).arg( persistentIndex( node
), place
);
680 QString
NodeHelper::fixEncoding( const QString
&encoding
)
682 QString returnEncoding
= encoding
;
683 // According to http://www.iana.org/assignments/character-sets, uppercase is
684 // preferred in MIME headers
685 if ( returnEncoding
.toUpper().contains( "ISO " ) ) {
686 returnEncoding
= returnEncoding
.toUpper();
687 returnEncoding
.replace( "ISO ", "ISO-" );
689 return returnEncoding
;
693 //-----------------------------------------------------------------------------
694 QString
NodeHelper::encodingForName( const QString
&descriptiveName
)
696 QString encoding
= KGlobal::charsets()->encodingForName( descriptiveName
);
697 return NodeHelper::fixEncoding( encoding
);
700 QStringList
NodeHelper::supportedEncodings(bool usAscii
)
702 QStringList encodingNames
= KGlobal::charsets()->availableEncodingNames();
703 QStringList encodings
;
704 QMap
<QString
,bool> mimeNames
;
705 QStringList::ConstIterator
constEnd( encodingNames
.constEnd() );
706 for (QStringList::ConstIterator it
= encodingNames
.constBegin();
707 it
!= constEnd
; ++it
)
709 QTextCodec
*codec
= KGlobal::charsets()->codecForName(*it
);
710 QString mimeName
= (codec
) ? QString(codec
->name()).toLower() : (*it
);
711 if (!mimeNames
.contains(mimeName
) )
713 encodings
.append( KGlobal::charsets()->descriptionForEncoding(*it
) );
714 mimeNames
.insert( mimeName
, true );
719 encodings
.prepend(KGlobal::charsets()->descriptionForEncoding("us-ascii") );
724 QByteArray
NodeHelper::autoDetectCharset(const QByteArray
&_encoding
, const QStringList
&encodingList
, const QString
&text
)
726 QStringList charsets
= encodingList
;
727 if (!_encoding
.isEmpty())
729 QString currentCharset
= QString::fromLatin1(_encoding
);
730 charsets
.removeAll(currentCharset
);
731 charsets
.prepend(currentCharset
);
734 QStringList::ConstIterator it
= charsets
.constBegin();
735 QStringList::ConstIterator end
= charsets
.constEnd();
736 for (; it
!= end
; ++it
)
738 QByteArray encoding
= (*it
).toLatin1();
739 if (encoding
== "locale")
741 encoding
= QTextCodec::codecForName( KGlobal::locale()->encoding() )->name();
742 kAsciiToLower(encoding
.data());
746 if (encoding
== "us-ascii") {
748 (void) toUsAscii(text
, &ok
);
754 const QTextCodec
*codec
= codecForName(encoding
);
756 kDebug() << "Auto-Charset: Something is wrong and I cannot get a codec:" << encoding
;
758 if (codec
->canEncode(text
))
766 QByteArray
NodeHelper::toUsAscii(const QString
& _str
, bool *ok
)
769 QString result
= _str
;
770 const int len
= result
.length();
771 for (int i
= 0; i
< len
; i
++)
772 if (result
.at(i
).unicode() >= 128) {
778 return result
.toLatin1();
781 QString
NodeHelper::fromAsString( KMime::Content
* node
)
783 KMime::Message
* topLevel
= dynamic_cast<KMime::Message
*>( node
->topLevel() );
785 return topLevel
->from()->asUnicodeString();
789 void NodeHelper::attachExtraContent( KMime::Content
*topLevelNode
, KMime::Content
* content
)
791 //kDebug() << "mExtraContents added for" << topLevelNode << " extra content: " << content;
792 mExtraContents
[topLevelNode
].append( content
);
795 void NodeHelper::removeAllExtraContent( KMime::Content
*topLevelNode
)
797 const QMap
< KMime::Content
*, QList
<KMime::Content
*> >::iterator it
798 = mExtraContents
.find( topLevelNode
);
799 if ( it
!= mExtraContents
.end() ) {
801 mExtraContents
.erase( it
);
805 QList
< KMime::Content
* > NodeHelper::extraContents( KMime::Content
*topLevelnode
) const
807 const QMap
< KMime::Content
*, QList
<KMime::Content
*> >::const_iterator it
808 = mExtraContents
.find( topLevelnode
);
809 if ( it
== mExtraContents
.end() )
810 return QList
<KMime::Content
*>();
815 bool NodeHelper::isPermanentWithExtraContent( KMime::Content
* node
) const
817 const QMap
< KMime::Content
*, QList
<KMime::Content
*> >::const_iterator it
818 = mExtraContents
.find( node
);
819 return it
!= mExtraContents
.end() && !it
->empty();
823 void NodeHelper::mergeExtraNodes( KMime::Content
*node
)
828 QList
<KMime::Content
* > extraNodes
= extraContents( node
);
829 Q_FOREACH( KMime::Content
* extra
, extraNodes
) {
830 if( node
->bodyIsMessage() ) {
831 kWarning() << "Asked to attach extra content to a kmime::message, this does not make sense. Attaching to:" << node
<<
832 node
->encodedContent() << "\n====== with =======\n" << extra
<< extra
->encodedContent();
835 KMime::Content
*c
= new KMime::Content( node
);
836 c
->setContent( extra
->encodedContent() );
838 node
->addContent( c
);
841 Q_FOREACH( KMime::Content
* child
, node
->contents() ) {
842 mergeExtraNodes( child
);
846 void NodeHelper::cleanFromExtraNodes( KMime::Content
* node
)
850 QList
<KMime::Content
* > extraNodes
= extraContents( node
);
851 Q_FOREACH( KMime::Content
* extra
, extraNodes
) {
852 QByteArray s
= extra
->encodedContent();
853 QList
<KMime::Content
* > children
= node
->contents();
854 Q_FOREACH( KMime::Content
*c
, children
) {
855 if ( c
->encodedContent() == s
) {
856 node
->removeContent( c
);
860 Q_FOREACH( KMime::Content
* child
, node
->contents() ) {
861 cleanFromExtraNodes( child
);
866 KMime::Message
* NodeHelper::messageWithExtraContent( KMime::Content
* topLevelNode
)
868 /*The merge is done in several steps:
869 1) merge the extra nodes into topLevelNode
870 2) copy the modified (merged) node tree into a new node tree
871 3) restore the original node tree in topLevelNode by removing the extra nodes from it
873 The reason is that extra nodes are assigned by pointer value to the nodes in the original tree.
878 mergeExtraNodes( topLevelNode
);
880 KMime::Message
*m
= new KMime::Message
;
881 m
->setContent( topLevelNode
->encodedContent() );
884 cleanFromExtraNodes( topLevelNode
);
885 // qDebug() << "MESSAGE WITH EXTRA: " << m->encodedContent();
886 // qDebug() << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
891 NodeHelper::AttachmentDisplayInfo
NodeHelper::attachmentDisplayInfo( KMime::Content
* node
)
893 AttachmentDisplayInfo info
;
894 info
.icon
= iconName( node
, KIconLoader::Small
);
895 info
.label
= fileName( node
);
896 if( info
.label
.isEmpty() ) {
897 info
.label
= node
->contentDescription()->asUnicodeString();
900 bool typeBlacklisted
= node
->contentType()->mediaType().toLower() == "multipart";
901 if ( !typeBlacklisted
) {
902 typeBlacklisted
= MessageCore::StringUtil::isCryptoPart( node
->contentType()->mediaType(),
903 node
->contentType()->subType(),
904 node
->contentDisposition()->filename() );
906 typeBlacklisted
= typeBlacklisted
|| node
== node
->topLevel();
907 const bool firstTextChildOfEncapsulatedMsg
=
908 node
->contentType()->mediaType().toLower() == "text" &&
909 node
->contentType()->subType().toLower() == "plain" &&
910 node
->parent() && node
->parent()->contentType()->mediaType().toLower() == "message";
911 typeBlacklisted
= typeBlacklisted
|| firstTextChildOfEncapsulatedMsg
;
912 info
.displayInHeader
= !info
.label
.isEmpty() && !info
.icon
.isEmpty() && !typeBlacklisted
;
916 KMime::Content
* NodeHelper::decryptedNodeForContent( KMime::Content
* content
) const
918 const QList
<KMime::Content
*> xc
= extraContents( content
);
920 if ( xc
.size() == 1 ) {
923 kWarning() << "WTF, encrypted node has multiple extra contents?";
929 bool NodeHelper::unencryptedMessage_helper( KMime::Content
*node
, QByteArray
&resultingData
, bool addHeaders
,
932 bool returnValue
= false;
934 KMime::Content
*curNode
= node
;
935 KMime::Content
*decryptedNode
= 0;
936 const QByteArray type
= node
->contentType( false ) ? QByteArray( node
->contentType()->mediaType() ).toLower() : "text";
937 const QByteArray subType
= node
->contentType( false ) ? node
->contentType()->subType().toLower() : "plain";
938 const bool isMultipart
= node
->contentType( false ) && node
->contentType()->isMultipart();
939 bool isSignature
= false;
941 //kDebug() << "(" << recursionLevel << ") Looking at" << type << "/" << subType;
944 if ( subType
== "signed" ) {
946 } else if ( subType
== "encrypted" ) {
947 decryptedNode
= decryptedNodeForContent( curNode
);
949 } else if ( type
== "application" ) {
950 if ( subType
== "octet-stream" ) {
951 decryptedNode
= decryptedNodeForContent( curNode
);
952 } else if ( subType
== "pkcs7-signature" ) {
954 } else if ( subType
== "pkcs7-mime" ) {
955 // note: subtype pkcs7-mime can also be signed
956 // and we do NOT want to remove the signature!
957 if ( encryptionState( curNode
) != KMMsgNotEncrypted
) {
958 decryptedNode
= decryptedNodeForContent( curNode
);
963 if ( decryptedNode
) {
964 //kDebug() << "Current node has an associated decrypted node, adding a modified header "
965 // "and then processing the children.";
967 Q_ASSERT( addHeaders
);
968 KMime::Content headers
;
969 headers
.setHead( curNode
->head() );
971 if ( decryptedNode
->contentType( false ) ) {
972 headers
.contentType()->from7BitString( decryptedNode
->contentType()->as7BitString( false ) );
974 headers
.removeHeader( headers
.contentType()->type() );
976 if ( decryptedNode
->contentTransferEncoding( false ) ) {
977 headers
.contentTransferEncoding()->from7BitString( decryptedNode
->contentTransferEncoding()->as7BitString( false ) );
979 headers
.removeHeader( headers
.contentTransferEncoding()->type() );
981 if ( decryptedNode
->contentDisposition( false ) ) {
982 headers
.contentDisposition()->from7BitString( decryptedNode
->contentDisposition()->as7BitString( false ) );
984 headers
.removeHeader( headers
.contentDisposition()->type() );
986 if ( decryptedNode
->contentDescription( false ) ) {
987 headers
.contentDescription()->from7BitString( decryptedNode
->contentDescription()->as7BitString( false ) );
989 headers
.removeHeader( headers
.contentDescription()->type() );
993 resultingData
+= headers
.head() + '\n';
994 unencryptedMessage_helper( decryptedNode
, resultingData
, false, recursionLevel
+ 1 );
999 else if ( isSignature
) {
1000 //kDebug() << "Current node is a signature, adding it as-is.";
1001 // We can't change the nodes under the signature, as that would invalidate it. Add the signature
1002 // and its child as-is
1004 resultingData
+= curNode
->head() + '\n';
1006 resultingData
+= curNode
->encodedBody();
1007 returnValue
= false;
1010 else if ( isMultipart
) {
1011 //kDebug() << "Current node is a multipart node, adding its header and then processing all children.";
1012 // Normal multipart node, add the header and all of its children
1013 bool somethingChanged
= false;
1015 resultingData
+= curNode
->head() + '\n';
1017 const QByteArray boundary
= curNode
->contentType()->boundary();
1018 foreach( KMime::Content
*child
, curNode
->contents() ) {
1019 resultingData
+= "\n--" + boundary
+ '\n';
1020 const bool changed
= unencryptedMessage_helper( child
, resultingData
, true, recursionLevel
+ 1 );
1022 somethingChanged
= true;
1025 resultingData
+= "\n--" + boundary
+ "--\n\n";
1026 returnValue
= somethingChanged
;
1029 else if ( curNode
->bodyIsMessage() ) {
1030 //kDebug() << "Current node is a message, adding the header and then processing the child.";
1032 resultingData
+= curNode
->head() + '\n';
1035 returnValue
= unencryptedMessage_helper( curNode
->bodyAsMessage().get(), resultingData
, true, recursionLevel
+ 1 );
1039 //kDebug() << "Current node is an ordinary leaf node, adding it as-is.";
1041 resultingData
+= curNode
->head() + '\n';
1043 resultingData
+= curNode
->body();
1044 returnValue
= false;
1048 //kDebug() << "(" << recursionLevel << ") done.";
1052 KMime::Message::Ptr
NodeHelper::unencryptedMessage( const KMime::Message::Ptr
& originalMessage
)
1054 QByteArray resultingData
;
1055 const bool messageChanged
= unencryptedMessage_helper( originalMessage
.get(), resultingData
, true );
1056 if ( messageChanged
) {
1058 kDebug() << "Resulting data is:" << resultingData
;
1059 QFile
bla("stripped.mbox");
1060 bla
.open(QIODevice::WriteOnly
);
1061 bla
.write(resultingData
);
1064 KMime::Message::Ptr
newMessage( new KMime::Message
);
1065 newMessage
->setContent( resultingData
);
1066 newMessage
->parse();
1069 return KMime::Message::Ptr();