Framework for looking up contacts directly in nepomuk in addition to going through...
[kdepim.git] / messageviewer / nodehelper.cpp
blob2619322e85b795a1c0fa7df2bb8fe9c6e30fd7e0
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"
26 #include "util.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>
36 #include <kdebug.h>
37 #include <kascii.h>
38 #include <ktemporaryfile.h>
39 #include <klocale.h>
40 #include <kcharsets.h>
41 #include <kde_file.h>
42 #include <kpimutils/kfileio.h>
44 #include <QDir>
45 #include <QTextCodec>
47 #include <string>
48 #include <sstream>
49 #include <algorithm>
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.
71 if ( mLocalCodec ) {
72 if ( mLocalCodec->name().toLower() == "eucjp"
73 #if defined Q_WS_WIN || defined Q_WS_MACX
74 || mLocalCodec->name().toLower() == "shift-jis" // OK?
75 #endif
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 )
92 if ( !node )
93 return;
94 mProcessedNodes.append( node );
95 //kDebug() << "Node processed: " << node->index().toString() << node->contentType()->as7BitString();
96 //<< " decodedContent" << node->decodedContent();
97 if ( recurse ) {
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 )
108 if ( !node )
109 return;
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();
117 if ( p )
118 p->removeContent( c );
120 qDeleteAll( it.value() );
121 //kDebug() << "mExtraContents deleted for" << it.key();
122 mExtraContents.erase( it );
125 //kDebug() << "Node UNprocessed: " << node;
126 if ( recurse ) {
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
137 if ( !node )
138 return true;
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();
146 it != end; ++it ) {
147 Interface::BodyPartMemento *memento = it.value();
148 memento->detach();
149 delete memento;
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();
169 if ( p )
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() )
223 return QString();
225 QString fileName = NodeHelper::fileName( node );
226 // strip off a leading path
227 int slashPos = fileName.lastIndexOf( '/' );
228 if( -1 != slashPos )
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 ) )
242 return QString();
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 );
249 return fname;
254 KUrl NodeHelper::tempFileUrlFromNode( const KMime::Content *node )
256 if (!node)
257 return KUrl();
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 );
264 if ( left != -1 )
265 left += 7;
267 QStringRef storedIndex( &path, left, right - left );
268 if ( left != -1 && storedIndex == index )
269 return KUrl( path );
271 return KUrl();
275 QString NodeHelper::createTempDir( const QString &param )
277 KTemporaryFile *tempFile = new KTemporaryFile();
278 tempFile->setSuffix( ".index." + param );
279 tempFile->open();
280 QString fname = tempFile->fileName();
281 delete tempFile;
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 );
294 return fname;
298 void NodeHelper::removeTempFiles()
300 QStringList::ConstIterator end = mTempFiles.constEnd();
301 for (QStringList::ConstIterator it = mTempFiles.constBegin(); it != end;
302 ++it)
304 QFile::remove(*it);
306 mTempFiles.clear();
307 end = mTempDirs.constEnd();
308 for (QStringList::ConstIterator it = mTempDirs.constBegin(); it != end;
309 ++it)
311 QDir(*it).rmdir(*it);
313 mTempDirs.clear();
316 void NodeHelper::addTempFile( const QString& file )
318 mTempFiles.append( file );
321 bool NodeHelper::isToltecMessage( KMime::Content* node )
323 if ( !node->contentType( false ) )
324 return false;
326 if ( node->contentType()->mediaType().toLower() != "multipart" ||
327 node->contentType()->subType().toLower() != "mixed" )
328 return false;
330 if ( node->contents().size() != 3 )
331 return false;
333 const KMime::Headers::Base *libraryHeader = node->headerByType( "X-Library" );
334 if ( !libraryHeader )
335 return false;
337 if ( QString::fromLatin1( libraryHeader->as7BitString( false ) ).toLower() !=
338 QLatin1String( "toltec" ) )
339 return false;
341 const KMime::Headers::Base *kolabTypeHeader = node->headerByType( "X-Kolab-Type" );
342 if ( !kolabTypeHeader )
343 return false;
345 if ( !QString::fromLatin1( kolabTypeHeader->as7BitString( false ) ).toLower().startsWith(
346 QLatin1String( "application/x-vnd.kolab" ) ) )
347 return false;
349 return true;
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 ) {
360 return true;
362 cur = cur->parent();
364 return false;
368 QByteArray NodeHelper::charset( KMime::Content *node )
370 if ( node->contentType( false ) )
371 return node->contentType( false )->charset();
372 else
373 return node->defaultCharset();
376 KMMsgEncryptionState NodeHelper::overallEncryptionState( KMime::Content *node ) const
378 KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
379 if ( !node )
380 return myState;
382 if( encryptionState( node ) == KMMsgNotEncrypted ) {
383 // NOTE: children are tested ONLY when parent is not encrypted
384 KMime::Content *child = MessageCore::NodeHelper::firstChild( node );
385 if ( child )
386 myState = overallEncryptionState( child );
387 else
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 );
395 if( next ) {
396 KMMsgEncryptionState otherState = overallEncryptionState( next );
397 switch( otherState ) {
398 case KMMsgEncryptionStateUnknown:
399 break;
400 case KMMsgNotEncrypted:
401 if( myState == KMMsgFullyEncrypted )
402 myState = KMMsgPartiallyEncrypted;
403 else if( myState != KMMsgPartiallyEncrypted )
404 myState = KMMsgNotEncrypted;
405 break;
406 case KMMsgPartiallyEncrypted:
407 myState = KMMsgPartiallyEncrypted;
408 break;
409 case KMMsgFullyEncrypted:
410 if( myState != KMMsgFullyEncrypted )
411 myState = KMMsgPartiallyEncrypted;
412 break;
413 case KMMsgEncryptionProblematic:
414 break;
418 //kDebug() <<"\n\n KMMsgEncryptionState:" << myState;
420 return myState;
424 KMMsgSignatureState NodeHelper::overallSignatureState( KMime::Content* node ) const
426 KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
427 if ( !node )
428 return myState;
430 if( signatureState( node ) == KMMsgNotSigned ) {
431 // children are tested ONLY when parent is not signed
432 KMime::Content* child = MessageCore::NodeHelper::firstChild( node );
433 if( child )
434 myState = overallSignatureState( child );
435 else
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 );
443 if( next ) {
444 KMMsgSignatureState otherState = overallSignatureState( next );
445 switch( otherState ) {
446 case KMMsgSignatureStateUnknown:
447 break;
448 case KMMsgNotSigned:
449 if( myState == KMMsgFullySigned )
450 myState = KMMsgPartiallySigned;
451 else if( myState != KMMsgPartiallySigned )
452 myState = KMMsgNotSigned;
453 break;
454 case KMMsgPartiallySigned:
455 myState = KMMsgPartiallySigned;
456 break;
457 case KMMsgFullySigned:
458 if( myState != KMMsgFullySigned )
459 myState = KMMsgPartiallySigned;
460 break;
461 case KMMsgSignatureProblematic:
462 break;
466 //kDebug() <<"\n\n KMMsgSignatureState:" << myState;
468 return myState;
471 QString NodeHelper::iconName( KMime::Content *node, int size )
473 if ( !node )
474 return QString();
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() );
491 // static
492 QString NodeHelper::replacePrefixes( const QString& str,
493 const QStringList& prefixRegExps,
494 bool replace,
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 );
510 } else { // valid rx
511 QString tmp = str;
512 if ( rx.indexIn( tmp ) == 0 ) {
513 recognized = true;
514 if ( replace )
515 return tmp.replace( 0, rx.matchedLength(), newPrefix + ' ' );
518 if ( !recognized )
519 return newPrefix + ' ' + str;
520 else
521 return 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,
531 bool replace,
532 const QString & newPrefix )
534 return NodeHelper::replacePrefixes( message->subject()->asUnicodeString(), prefixRegExps, replace,
535 newPrefix );
538 void NodeHelper::setOverrideCodec( KMime::Content* node, const QTextCodec* codec )
540 if ( !node )
541 return;
543 mOverrideCodecs[node] = codec;
546 const QTextCodec * NodeHelper::codec( KMime::Content* node )
548 if (! node )
549 return mLocalCodec;
551 const QTextCodec *c = mOverrideCodecs.value( node, 0 );
552 if ( !c ) {
553 // no override-codec set for this message, try the CT charset parameter:
554 c = codecForName( node->contentType()->charset() );
556 if ( !c ) {
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() );
561 if ( !c ) {
562 // no charset means us-ascii (RFC 2045), so using local encoding should
563 // be okay
564 c = mLocalCodec;
566 return c;
569 const QTextCodec* NodeHelper::codecForName(const QByteArray& _str)
571 if (_str.isEmpty())
572 return 0;
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();
585 return name;
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() )
595 return 0;
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() ) {
612 delete it.value();
613 if ( memento ) {
614 it.value() = memento;
615 } else {
616 mementos.erase( it );
618 } else {
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 );
634 else
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 );
647 } else {
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
660 if ( !node )
661 return QString();
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) );
672 return indexStr;
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 );
717 encodings.sort();
718 if (usAscii)
719 encodings.prepend(KGlobal::charsets()->descriptionForEncoding("us-ascii") );
720 return encodings;
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());
744 if (text.isEmpty())
745 return encoding;
746 if (encoding == "us-ascii") {
747 bool ok;
748 (void) toUsAscii(text, &ok);
749 if (ok)
750 return encoding;
752 else
754 const QTextCodec *codec = codecForName(encoding);
755 if (!codec) {
756 kDebug() << "Auto-Charset: Something is wrong and I cannot get a codec:" << encoding;
757 } else {
758 if (codec->canEncode(text))
759 return encoding;
763 return 0;
766 QByteArray NodeHelper::toUsAscii(const QString& _str, bool *ok)
768 bool all_ok =true;
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) {
773 result[i] = '?';
774 all_ok = false;
776 if (ok)
777 *ok = all_ok;
778 return result.toLatin1();
781 QString NodeHelper::fromAsString( KMime::Content* node )
783 KMime::Message* topLevel = dynamic_cast<KMime::Message*>( node->topLevel() );
784 if ( topLevel )
785 return topLevel->from()->asUnicodeString();
786 return QString();
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() ) {
800 qDeleteAll( *it );
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*>();
811 else
812 return *it;
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 )
825 if ( !node )
826 return;
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();
833 continue;
835 KMime::Content *c = new KMime::Content( node );
836 c->setContent( extra->encodedContent() );
837 c->parse();
838 node->addContent( c );
841 Q_FOREACH( KMime::Content* child, node->contents() ) {
842 mergeExtraNodes( child );
846 void NodeHelper::cleanFromExtraNodes( KMime::Content* node )
848 if ( !node )
849 return;
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.
875 if (!topLevelNode)
876 return 0;
878 mergeExtraNodes( topLevelNode );
880 KMime::Message *m = new KMime::Message;
881 m->setContent( topLevelNode->encodedContent() );
882 m->parse();
884 cleanFromExtraNodes( topLevelNode );
885 // qDebug() << "MESSAGE WITH EXTRA: " << m->encodedContent();
886 // qDebug() << "MESSAGE WITHOUT EXTRA: " << topLevelNode->encodedContent();
888 return m;
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;
913 return info;
916 KMime::Content * NodeHelper::decryptedNodeForContent( KMime::Content * content ) const
918 const QList<KMime::Content*> xc = extraContents( content );
919 if ( !xc.empty() ) {
920 if ( xc.size() == 1 ) {
921 return xc.front();
922 } else {
923 kWarning() << "WTF, encrypted node has multiple extra contents?";
926 return 0;
929 bool NodeHelper::unencryptedMessage_helper( KMime::Content *node, QByteArray &resultingData, bool addHeaders,
930 int recursionLevel )
932 bool returnValue = false;
933 if ( node ) {
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;
943 if ( isMultipart ) {
944 if ( subType == "signed" ) {
945 isSignature = true;
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" ) {
953 isSignature = true;
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() );
970 headers.parse();
971 if ( decryptedNode->contentType( false ) ) {
972 headers.contentType()->from7BitString( decryptedNode->contentType()->as7BitString( false ) );
973 } else {
974 headers.removeHeader( headers.contentType()->type() );
976 if ( decryptedNode->contentTransferEncoding( false ) ) {
977 headers.contentTransferEncoding()->from7BitString( decryptedNode->contentTransferEncoding()->as7BitString( false ) );
978 } else {
979 headers.removeHeader( headers.contentTransferEncoding()->type() );
981 if ( decryptedNode->contentDisposition( false ) ) {
982 headers.contentDisposition()->from7BitString( decryptedNode->contentDisposition()->as7BitString( false ) );
983 } else {
984 headers.removeHeader( headers.contentDisposition()->type() );
986 if ( decryptedNode->contentDescription( false ) ) {
987 headers.contentDescription()->from7BitString( decryptedNode->contentDescription()->as7BitString( false ) );
988 } else {
989 headers.removeHeader( headers.contentDescription()->type() );
991 headers.assemble();
993 resultingData += headers.head() + '\n';
994 unencryptedMessage_helper( decryptedNode, resultingData, false, recursionLevel + 1 );
996 returnValue = true;
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
1003 if ( addHeaders ) {
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;
1014 if ( addHeaders ) {
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 );
1021 if ( changed ) {
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.";
1031 if ( addHeaders ) {
1032 resultingData += curNode->head() + '\n';
1035 returnValue = unencryptedMessage_helper( curNode->bodyAsMessage().get(), resultingData, true, recursionLevel + 1 );
1038 else {
1039 //kDebug() << "Current node is an ordinary leaf node, adding it as-is.";
1040 if ( addHeaders ) {
1041 resultingData += curNode->head() + '\n';
1043 resultingData += curNode->body();
1044 returnValue = false;
1048 //kDebug() << "(" << recursionLevel << ") done.";
1049 return returnValue;
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 ) {
1057 #if 0
1058 kDebug() << "Resulting data is:" << resultingData;
1059 QFile bla("stripped.mbox");
1060 bla.open(QIODevice::WriteOnly);
1061 bla.write(resultingData);
1062 bla.close();
1063 #endif
1064 KMime::Message::Ptr newMessage( new KMime::Message );
1065 newMessage->setContent( resultingData );
1066 newMessage->parse();
1067 return newMessage;
1068 } else {
1069 return KMime::Message::Ptr();