1/*
2 kmime_content.cpp
3
4 KMime, the KDE Internet mail/usenet news message library.
5 Copyright (c) 2001 the KMime authors.
6 See file AUTHORS for details
7 Copyright (c) 2006 Volker Krause <vkrause@kde.org>
8 Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
9
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public
12 License as published by the Free Software Foundation; either
13 version 2 of the License, or (at your option) any later version.
14
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Library General Public License for more details.
19
20 You should have received a copy of the GNU Library General Public License
21 along with this library; see the file COPYING.LIB. If not, write to
22 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 Boston, MA 02110-1301, USA.
24*/
25/**
26 @file
27 This file is part of the API for handling @ref MIME data and
28 defines the Content class.
29
30 @brief
31 Defines the Content class.
32
33 @authors the KMime authors (see AUTHORS file),
34 Volker Krause \<vkrause@kde.org\>
35*/
36
37#include "kmime_content.h"
38#include "kmime_content_p.h"
39#include "kmime_codecs.h"
40#include "kmime_message.h"
41#include "kmime_header_parsing.h"
42#include "kmime_header_parsing_p.h"
43#include "kmime_parsers.h"
44#include "kmime_util_p.h"
45
46#include <kcharsets.h>
47#include <kcodecs.h>
48#include <kglobal.h>
49#include <klocale.h>
50#include <klocalizedstring.h>
51#include <kdebug.h>
52
53#include <QtCore/QTextCodec>
54#include <QtCore/QTextStream>
55#include <QtCore/QByteArray>
56
57using namespace KMime;
58
59namespace KMime {
60
61Content::Content()
62 : d_ptr( new ContentPrivate( this ) )
63{
64}
65
66Content::Content( Content *parent )
67 : d_ptr( new ContentPrivate( this ) )
68{
69 d_ptr->parent = parent;
70}
71
72Content::Content( const QByteArray &h, const QByteArray &b )
73 : d_ptr( new ContentPrivate( this ) )
74{
75 d_ptr->head = h;
76 d_ptr->body = b;
77}
78
79Content::Content( const QByteArray &h, const QByteArray &b, Content *parent )
80 : d_ptr( new ContentPrivate( this ) )
81{
82 d_ptr->head = h;
83 d_ptr->body = b;
84 d_ptr->parent = parent;
85}
86
87Content::Content( ContentPrivate *d )
88 : d_ptr( d )
89{
90}
91
92Content::~Content()
93{
94 qDeleteAll( h_eaders );
95 h_eaders.clear();
96 delete d_ptr;
97 d_ptr = 0;
98}
99
100bool Content::hasContent() const
101{
102 return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents().isEmpty();
103}
104
105void Content::setContent( const QList<QByteArray> &l )
106{
107 Q_D( Content );
108 //qDebug("Content::setContent( const QList<QByteArray> &l ) : start");
109 d->head.clear();
110 d->body.clear();
111
112 //usage of textstreams is much faster than simply appending the strings
113 QTextStream hts( &( d->head ), QIODevice::WriteOnly );
114 QTextStream bts( &( d->body ), QIODevice::WriteOnly );
115 hts.setCodec( "ISO 8859-1" );
116 bts.setCodec( "ISO 8859-1" );
117
118 bool isHead = true;
119 foreach ( const QByteArray& line, l ) {
120 if ( isHead && line.isEmpty() ) {
121 isHead = false;
122 continue;
123 }
124 if ( isHead ) {
125 hts << line << "\n";
126 } else {
127 bts << line << "\n";
128 }
129 }
130
131 //qDebug("Content::setContent( const QList<QByteArray> & l ) : finished");
132}
133
134void Content::setContent( const QByteArray &s )
135{
136 Q_D( Content );
137 KMime::HeaderParsing::extractHeaderAndBody( s, d->head, d->body );
138}
139
140QByteArray Content::head() const
141{
142 return d_ptr->head;
143}
144
145void Content::setHead( const QByteArray &head )
146{
147 d_ptr->head = head;
148 if ( !head.endsWith( '\n' ) ) {
149 d_ptr->head += '\n';
150 }
151}
152
153QByteArray Content::body() const
154{
155 return d_ptr->body;
156}
157
158void Content::setBody( const QByteArray &body )
159{
160 d_ptr->body = body;
161}
162
163QByteArray Content::preamble() const
164{
165 return d_ptr->preamble;
166}
167
168void Content::setPreamble( const QByteArray &preamble )
169{
170 d_ptr->preamble = preamble;
171}
172
173
174QByteArray Content::epilogue() const
175{
176 return d_ptr->epilogue;
177}
178
179void Content::setEpilogue( const QByteArray &epilogue )
180{
181 d_ptr->epilogue = epilogue;
182}
183
184void Content::parse()
185{
186 Q_D( Content );
187
188 // Clean up old headers and parse them again.
189 qDeleteAll( h_eaders );
190 h_eaders.clear();
191 h_eaders = HeaderParsing::parseHeaders( d->head );
192 foreach ( Headers::Base *h, h_eaders ) {
193 h->setParent( this );
194 }
195
196 // If we are frozen, save the body as-is. This is done because parsing
197 // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.)
198 if ( d->frozen ) {
199 d->frozenBody = d->body;
200 }
201
202 // Clean up old sub-Contents and parse them again.
203 qDeleteAll( d->multipartContents );
204 d->multipartContents.clear();
205 d->clearBodyMessage();
206 Headers::ContentType *ct = contentType();
207 if ( ct->isText() ) {
208 // This content is either text, or of unknown type.
209
210 if ( d->parseUuencoded() ) {
211 // This is actually uuencoded content generated by broken software.
212 } else if ( d->parseYenc() ) {
213 // This is actually yenc content generated by broken software.
214 } else {
215 // This is just plain text.
216 }
217 } else if ( ct->isMultipart() ) {
218 // This content claims to be MIME multipart.
219
220 if ( d->parseMultipart() ) {
221 // This is actual MIME multipart content.
222 } else {
223 // Parsing failed; treat this content as "text/plain".
224 ct->setMimeType( "text/plain" );
225 ct->setCharset( "US-ASCII" );
226 }
227 } else {
228 // This content is something else, like an encapsulated message or a binary attachment
229 // or something like that
230 if ( bodyIsMessage() ) {
231 d->bodyAsMessage = Message::Ptr( new Message );
232 d->bodyAsMessage->setContent( d->body );
233 d->bodyAsMessage->setFrozen( d->frozen );
234 d->bodyAsMessage->parse();
235 d->bodyAsMessage->d_ptr->parent = this;
236
237 // Clear the body, as it is now represented by d->bodyAsMessage. This is the same behavior
238 // as with multipart contents, since parseMultipart() clears the body as well
239 d->body.clear();
240 }
241 }
242}
243
244bool Content::isFrozen() const
245{
246 return d_ptr->frozen;
247}
248
249void Content::setFrozen( bool frozen )
250{
251 d_ptr->frozen = frozen;
252}
253
254void Content::assemble()
255{
256 Q_D( Content );
257 if ( d->frozen ) {
258 return;
259 }
260
261 d->head = assembleHeaders();
262 foreach ( Content *c, contents() ) {
263 c->assemble();
264 }
265}
266
267QByteArray Content::assembleHeaders()
268{
269 QByteArray newHead;
270 foreach ( const Headers::Base *h, h_eaders ) {
271 if ( !h->isEmpty() ) {
272 newHead += h->as7BitString() + '\n';
273 }
274 }
275
276 return newHead;
277}
278
279void Content::clear()
280{
281 Q_D( Content );
282 qDeleteAll( h_eaders );
283 h_eaders.clear();
284 clearContents();
285 d->head.clear();
286 d->body.clear();
287}
288
289void Content::clearContents( bool del )
290{
291 Q_D( Content );
292 if ( del ) {
293 qDeleteAll( d->multipartContents );
294 }
295 d->multipartContents.clear();
296 d->clearBodyMessage();
297}
298
299QByteArray Content::encodedContent( bool useCrLf )
300{
301 Q_D( Content );
302 QByteArray e;
303
304 // Head.
305 e = d->head;
306 e += '\n';
307 e += encodedBody();
308
309 if ( useCrLf ) {
310 return LFtoCRLF( e );
311 } else {
312 return e;
313 }
314}
315
316QByteArray Content::encodedBody()
317{
318 Q_D( Content );
319 QByteArray e;
320 // Body.
321 if ( d->frozen ) {
322 // This Content is frozen.
323 if ( d->frozenBody.isEmpty() ) {
324 // This Content has never been parsed.
325 e += d->body;
326 } else {
327 // Use the body as it was before parsing.
328 e += d->frozenBody;
329 }
330 } else if ( bodyIsMessage() && d->bodyAsMessage ) {
331 // This is an encapsulated message
332 // No encoding needed, as the ContentTransferEncoding can only be 7bit
333 // for encapsulated messages
334 e += d->bodyAsMessage->encodedContent();
335 } else if ( !d->body.isEmpty() ) {
336 // This is a single-part Content.
337 Headers::ContentTransferEncoding *enc = contentTransferEncoding();
338
339 if ( enc->needToEncode() ) {
340 if ( enc->encoding() == Headers::CEquPr ) {
341 e += KCodecs::quotedPrintableEncode( d->body, false );
342 } else {
343 e += KCodecs::base64Encode( d->body, true );
344 e += '\n';
345 }
346 } else {
347 e += d->body;
348 }
349 }
350
351 if ( !d->frozen && !d->multipartContents.isEmpty() ) {
352 // This is a multipart Content.
353 Headers::ContentType *ct=contentType();
354 QByteArray boundary = "\n--" + ct->boundary();
355
356 if ( !d->preamble.isEmpty() ) {
357 e += d->preamble;
358 }
359
360 //add all (encoded) contents separated by boundaries
361 foreach ( Content *c, d->multipartContents ) {
362 e += boundary + '\n';
363 e += c->encodedContent( false ); // don't convert LFs here, we do that later!!!!!
364 }
365 //finally append the closing boundary
366 e += boundary+"--\n";
367
368 if ( !d->epilogue.isEmpty() ) {
369 e += d->epilogue;
370 }
371 }
372 return e;
373}
374
375QByteArray Content::decodedContent()
376{
377 QByteArray ret;
378 Headers::ContentTransferEncoding *ec=contentTransferEncoding();
379 bool removeTrailingNewline=false;
380
381 if ( d_ptr->body.length() == 0 ) {
382 return ret;
383 }
384
385 if ( ec->decoded() ) {
386 ret = d_ptr->body;
387 //Laurent Fix bug #311267
388 //removeTrailingNewline = true;
389 } else {
390 switch ( ec->encoding() ) {
391 case Headers::CEbase64 :
392 {
393 KMime::Codec *codec = KMime::Codec::codecForName( "base64" );
394 Q_ASSERT( codec );
395 ret.resize( codec->maxDecodedSizeFor( d_ptr->body.size() ) );
396 KMime::Decoder* decoder = codec->makeDecoder();
397 QByteArray::const_iterator inputIt = d_ptr->body.constBegin();
398 QByteArray::iterator resultIt = ret.begin();
399 decoder->decode( inputIt, d_ptr->body.constEnd(), resultIt, ret.end() );
400 ret.truncate( resultIt - ret.begin() );
401 break;
402 }
403 case Headers::CEquPr :
404 ret = KCodecs::quotedPrintableDecode( d_ptr->body );
405 removeTrailingNewline = true;
406 break;
407 case Headers::CEuuenc :
408 KCodecs::uudecode( d_ptr->body, ret );
409 break;
410 case Headers::CEbinary :
411 ret = d_ptr->body;
412 removeTrailingNewline = false;
413 break;
414 default :
415 ret = d_ptr->body;
416 removeTrailingNewline = true;
417 }
418 }
419
420 if ( removeTrailingNewline && ( ret.size() > 0 ) && ( ret[ret.size() - 1] == '\n' ) ) {
421 ret.resize( ret.size() - 1 );
422 }
423
424 return ret;
425}
426
427QString Content::decodedText( bool trimText, bool removeTrailingNewlines )
428{
429 if ( !decodeText() ) { //this is not a text content !!
430 return QString();
431 }
432
433 bool ok = true;
434 QTextCodec *codec =
435 KGlobal::charsets()->codecForName( QLatin1String( contentType()->charset() ), ok );
436 if ( !ok || codec == NULL ) { // no suitable codec found => try local settings and hope the best ;-)
437 codec = KGlobal::locale()->codecForEncoding();
438 QByteArray chset = KGlobal::locale()->encoding();
439 contentType()->setCharset( chset );
440 }
441
442 QString s = codec->toUnicode( d_ptr->body.data(), d_ptr->body.length() );
443
444 if ( trimText || removeTrailingNewlines ) {
445 int i;
446 for ( i = s.length() - 1; i >= 0; --i ) {
447 if ( trimText ) {
448 if ( !s[i].isSpace() ) {
449 break;
450 }
451 }
452 else {
453 if ( s[i] != QLatin1Char( '\n' ) ) {
454 break;
455 }
456 }
457 }
458 s.truncate( i + 1 );
459 } else {
460 if ( s.right( 1 ) == QLatin1String( "\n" ) ) {
461 s.truncate( s.length() - 1 ); // remove trailing new-line
462 }
463 }
464
465 return s;
466}
467
468void Content::fromUnicodeString( const QString &s )
469{
470 bool ok = true;
471 QTextCodec *codec =
472 KGlobal::charsets()->codecForName( QLatin1String( contentType()->charset() ), ok );
473
474 if ( !ok ) { // no suitable codec found => try local settings and hope the best ;-)
475 codec = KGlobal::locale()->codecForEncoding();
476 QByteArray chset = KGlobal::locale()->encoding();
477 contentType()->setCharset( chset );
478 }
479
480 d_ptr->body = codec->fromUnicode( s );
481 contentTransferEncoding()->setDecoded( true ); //text is always decoded
482}
483
484Content *Content::textContent()
485{
486 Content *ret=0;
487
488 //return the first content with mimetype=text/*
489 if ( contentType()->isText() ) {
490 ret = this;
491 } else {
492 foreach ( Content *c, d_ptr->contents() ) {
493 if ( ( ret = c->textContent() ) != 0 ) {
494 break;
495 }
496 }
497 }
498 return ret;
499}
500
501Content::List Content::attachments( bool incAlternatives )
502{
503 List attachments;
504 if ( d_ptr->contents().isEmpty() ) {
505 attachments.append( this );
506 } else {
507 foreach ( Content *c, d_ptr->contents() ) {
508 if ( !incAlternatives &&
509 c->contentType()->category() == Headers::CCalternativePart ) {
510 continue;
511 } else {
512 attachments += c->attachments( incAlternatives );
513 }
514 }
515 }
516
517 if ( isTopLevel() ) {
518 Content *text = textContent();
519 if ( text ) {
520 attachments.removeAll( text );
521 }
522 }
523 return attachments;
524}
525
526Content::List Content::contents() const
527{
528 return d_ptr->contents();
529}
530
531void Content::addContent( Content *c, bool prepend )
532{
533 Q_D( Content );
534
535 // This method makes no sense for encapsulated messages
536 Q_ASSERT( !bodyIsMessage() );
537
538 // If this message is single-part; make it multipart first.
539 if( d->multipartContents.isEmpty() && !contentType()->isMultipart() ) {
540 // The current body will be our first sub-Content.
541 Content *main = new Content( this );
542
543 // Move the MIME headers to the newly created sub-Content.
544 // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers
545 // are not moved to the subcontent; they remain with the top-level content.
546 for ( Headers::Base::List::iterator it = h_eaders.begin();
547 it != h_eaders.end(); ) {
548 if ( (*it)->isMimeHeader() ) {
549 // Add to new content.
550 main->setHeader( *it );
551 // Remove from this content.
552 it = h_eaders.erase( it );
553 } else {
554 ++it;
555 }
556 }
557
558 // Adjust the Content-Type of the newly created sub-Content.
559 main->contentType()->setCategory( Headers::CCmixedPart );
560
561 // Move the body to the new subcontent.
562 main->setBody( d->body );
563 d->body.clear();
564
565 // Add the subcontent.
566 d->multipartContents.append( main );
567
568 // Convert this content to "multipart/mixed".
569 Headers::ContentType *ct = contentType();
570 ct->setMimeType( "multipart/mixed" );
571 ct->setBoundary( multiPartBoundary() );
572 ct->setCategory( Headers::CCcontainer );
573 contentTransferEncoding()->clear(); // 7Bit, decoded.
574 }
575
576 // Add the new content.
577 if( prepend ) {
578 d->multipartContents.prepend( c );
579 } else {
580 d->multipartContents.append( c );
581 }
582
583 if( c->parent() != this ) {
584 // If the content was part of something else, this will remove it from there.
585 c->setParent( this );
586 }
587}
588
589void Content::removeContent( Content *c, bool del )
590{
591 Q_D( Content );
592 if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( c ) ) {
593 return;
594 }
595
596 // This method makes no sense for encapsulated messages.
597 // Should be covered by the above assert already, though.
598 Q_ASSERT( !bodyIsMessage() );
599
600 d->multipartContents.removeAll( c );
601 if ( del ) {
602 delete c;
603 } else {
604 c->d_ptr->parent = 0;
605 }
606
607 // If only one content is left, turn this content into a single-part.
608 if( d->multipartContents.count() == 1 ) {
609 Content *main = d->multipartContents.first();
610
611 // Move all headers from the old subcontent to ourselves.
612 // NOTE: This also sets the new Content-Type.
613 foreach( Headers::Base *h, main->h_eaders ) {
614 setHeader( h ); // Will remove the old one if present.
615 }
616 main->h_eaders.clear();
617
618 // Move the body.
619 d->body = main->body();
620
621 // Delete the old subcontent.
622 delete main;
623 d->multipartContents.clear();
624 }
625}
626
627void Content::changeEncoding( Headers::contentEncoding e )
628{
629 // This method makes no sense for encapsulated messages, they are always 7bit
630 // encoded.
631 Q_ASSERT( !bodyIsMessage() );
632
633 Headers::ContentTransferEncoding *enc = contentTransferEncoding();
634 if( enc->encoding() == e ) {
635 // Nothing to do.
636 return;
637 }
638
639 if( decodeText() ) {
640 // This is textual content. Textual content is stored decoded.
641 Q_ASSERT( enc->decoded() );
642 enc->setEncoding( e );
643 } else {
644 // This is non-textual content. Re-encode it.
645 if( e == Headers::CEbase64 ) {
646 d_ptr->body = KCodecs::base64Encode( decodedContent(), true );
647 d_ptr->body.append( "\n" );
648 enc->setEncoding( e );
649 enc->setDecoded( false );
650 } else {
651 // It only makes sense to convert binary stuff to base64.
652 Q_ASSERT( false );
653 }
654 }
655}
656
657void Content::toStream( QTextStream &ts, bool scrambleFromLines )
658{
659 QByteArray ret = encodedContent( false );
660
661 if ( scrambleFromLines ) {
662 // FIXME Why are only From lines with a preceding empty line considered?
663 // And, of course, all lines starting with >*From have to be escaped
664 // because otherwise the transformation is not revertable.
665 ret.replace( "\n\nFrom ", "\n\n>From ");
666 }
667 ts << ret;
668}
669
670Headers::Generic *Content::getNextHeader( QByteArray &head )
671{
672 return d_ptr->nextHeader( head );
673}
674
675Headers::Generic *Content::nextHeader( QByteArray &head )
676{
677 return d_ptr->nextHeader( head );
678}
679
680Headers::Generic *ContentPrivate::nextHeader( QByteArray &_head )
681{
682 Headers::Base *header = HeaderParsing::extractFirstHeader( _head );
683 if ( !header ) {
684 return 0;
685 }
686 // Convert it from the real class to Generic.
687 Headers::Generic *ret = new Headers::Generic( header->type(), q_ptr );
688 ret->from7BitString( header->as7BitString() );
689 return ret;
690}
691
692Headers::Base *Content::getHeaderByType( const char *type )
693{
694 return headerByType( type );
695}
696
697Headers::Base *Content::headerByType( const char *type )
698{
699 Q_ASSERT( type && *type );
700
701 foreach( Headers::Base *h, h_eaders ) {
702 if( h->is( type ) ) {
703 return h; // Found.
704 }
705 }
706
707 return 0; // Not found.
708}
709
710Headers::Base::List Content::headersByType( const char *type )
711{
712 Q_ASSERT( type && *type );
713
714 Headers::Base::List result;
715
716 foreach( Headers::Base *h, h_eaders ) {
717 if( h->is( type ) ) {
718 result << h;
719 }
720 }
721
722 return result;
723}
724
725void Content::setHeader( Headers::Base *h )
726{
727 Q_ASSERT( h );
728 removeHeader( h->type() );
729 appendHeader( h );
730}
731
732void Content::appendHeader( Headers::Base *h )
733{
734 h_eaders.append( h );
735 h->setParent( this );
736}
737
738void Content::prependHeader( Headers::Base *h )
739{
740 h_eaders.prepend( h );
741 h->setParent( this );
742}
743
744bool Content::removeHeader( const char *type )
745{
746 for ( Headers::Base::List::iterator it = h_eaders.begin();
747 it != h_eaders.end(); ++it )
748 if ( (*it)->is(type) ) {
749 delete (*it);
750 h_eaders.erase( it );
751 return true;
752 }
753
754 return false;
755}
756
757bool Content::hasHeader( const char *type )
758{
759 return headerByType( type ) != 0;
760}
761
762int Content::size()
763{
764 int ret = d_ptr->body.length();
765
766 if ( contentTransferEncoding()->encoding() == Headers::CEbase64 ) {
767 KMime::Codec *codec = KMime::Codec::codecForName( "base64" );
768 return codec->maxEncodedSizeFor(ret);
769 }
770
771 // Not handling quoted-printable here since that requires actually
772 // converting the content, and that is O(size_of_content).
773 // For quoted-printable, this is only an approximate size.
774
775 return ret;
776}
777
778int Content::storageSize() const
779{
780 const Q_D( Content );
781 int s = d->head.size();
782
783 if ( d->contents().isEmpty() ) {
784 s += d->body.size();
785 } else {
786
787 // FIXME: This should take into account the boundary headers that are added in
788 // encodedContent!
789 foreach ( Content *c, d->contents() ) {
790 s += c->storageSize();
791 }
792 }
793
794 return s;
795}
796
797int Content::lineCount() const
798{
799 const Q_D( Content );
800 int ret = 0;
801 if ( !isTopLevel() ) {
802 ret += d->head.count( '\n' );
803 }
804 ret += d->body.count( '\n' );
805
806 foreach ( Content *c, d->contents() ) {
807 ret += c->lineCount();
808 }
809
810 return ret;
811}
812
813QByteArray Content::rawHeader( const char *name ) const
814{
815 return KMime::extractHeader( d_ptr->head, name );
816}
817
818QList<QByteArray> Content::rawHeaders( const char *name ) const
819{
820 return KMime::extractHeaders( d_ptr->head, name );
821}
822
823bool Content::decodeText()
824{
825 Q_D( Content );
826 Headers::ContentTransferEncoding *enc = contentTransferEncoding();
827
828 if ( !contentType()->isText() ) {
829 return false; //non textual data cannot be decoded here => use decodedContent() instead
830 }
831 if ( enc->decoded() ) {
832 return true; //nothing to do
833 }
834
835 switch( enc->encoding() )
836 {
837 case Headers::CEbase64 :
838 d->body = KCodecs::base64Decode( d->body );
839 d->body.append( "\n" );
840 break;
841 case Headers::CEquPr :
842 d->body = KCodecs::quotedPrintableDecode( d->body );
843 break;
844 case Headers::CEuuenc :
845 d->body = KCodecs::uudecode( d->body );
846 d->body.append( "\n" );
847 break;
848 case Headers::CEbinary :
849 // nothing to decode
850 d->body.append( "\n" );
851 default :
852 break;
853 }
854 enc->setDecoded( true );
855 return true;
856}
857
858QByteArray Content::defaultCharset() const
859{
860 return d_ptr->defaultCS;
861}
862
863void Content::setDefaultCharset( const QByteArray &cs )
864{
865 d_ptr->defaultCS = KMime::cachedCharset( cs );
866
867 foreach ( Content *c, d_ptr->contents() ) {
868 c->setDefaultCharset( cs );
869 }
870
871 // reparse the part and its sub-parts in order
872 // to clear cached header values
873 parse();
874}
875
876bool Content::forceDefaultCharset() const
877{
878 return d_ptr->forceDefaultCS;
879}
880
881void Content::setForceDefaultCharset( bool b )
882{
883 d_ptr->forceDefaultCS = b;
884
885 foreach ( Content *c, d_ptr->contents() ) {
886 c->setForceDefaultCharset( b );
887 }
888
889 // reparse the part and its sub-parts in order
890 // to clear cached header values
891 parse();
892}
893
894Content * KMime::Content::content( const ContentIndex &index ) const
895{
896 if ( !index.isValid() ) {
897 return const_cast<KMime::Content*>( this );
898 }
899 ContentIndex idx = index;
900 unsigned int i = idx.pop() - 1; // one-based -> zero-based index
901 if ( i < (unsigned int)d_ptr->contents().size() ) {
902 return d_ptr->contents()[i]->content( idx );
903 } else {
904 return 0;
905 }
906}
907
908ContentIndex KMime::Content::indexForContent( Content * content ) const
909{
910 int i = d_ptr->contents().indexOf( content );
911 if ( i >= 0 ) {
912 ContentIndex ci;
913 ci.push( i + 1 ); // zero-based -> one-based index
914 return ci;
915 }
916 // not found, we need to search recursively
917 for ( int i = 0; i < d_ptr->contents().size(); ++i ) {
918 ContentIndex ci = d_ptr->contents()[i]->indexForContent( content );
919 if ( ci.isValid() ) {
920 // found it
921 ci.push( i + 1 ); // zero-based -> one-based index
922 return ci;
923 }
924 }
925 return ContentIndex(); // not found
926}
927
928bool Content::isTopLevel() const
929{
930 return d_ptr->parent == 0;
931}
932
933void Content::setParent( Content *parent )
934{
935 // Make sure the Content is only in the contents list of one parent object
936 Content *oldParent = d_ptr->parent;
937 if ( oldParent ) {
938 if ( !oldParent->contents().isEmpty() && oldParent->contents().contains( this ) ) {
939 oldParent->removeContent( this );
940 }
941 }
942
943 d_ptr->parent = parent;
944 if ( parent ) {
945 if ( !parent->contents().isEmpty() && !parent->contents().contains( this ) ) {
946 parent->addContent( this );
947 }
948 }
949}
950
951Content *Content::parent() const
952{
953 return d_ptr->parent;
954}
955
956Content *Content::topLevel() const
957{
958 Content *top = const_cast<Content*>(this);
959 Content *c = parent();
960 while ( c ) {
961 top = c;
962 c = c->parent();
963 }
964
965 return top;
966}
967
968ContentIndex Content::index() const
969{
970 Content* top = topLevel();
971 if ( top ) {
972 return top->indexForContent( const_cast<Content*>(this) );
973 }
974
975 return indexForContent( const_cast<Content*>(this) );
976}
977
978Message::Ptr Content::bodyAsMessage() const
979{
980 if ( bodyIsMessage() && d_ptr->bodyAsMessage ) {
981 return d_ptr->bodyAsMessage;
982 } else {
983 return Message::Ptr();
984 }
985}
986
987bool Content::bodyIsMessage() const
988{
989 // Use const_case here to work around API issue that neither header() nor hasHeader() are
990 // const, even though they should be
991 return const_cast<Content*>( this )->header<Headers::ContentType>( false ) &&
992 const_cast<Content*>( this )->header<Headers::ContentType>( true )
993 ->mimeType().toLower() == "message/rfc822";
994}
995
996// @cond PRIVATE
997#define kmime_mk_header_accessor( type, method ) \
998Headers::type *Content::method( bool create ) { \
999 return header<Headers::type>( create ); \
1000}
1001
1002kmime_mk_header_accessor( ContentType, contentType )
1003kmime_mk_header_accessor( ContentTransferEncoding, contentTransferEncoding )
1004kmime_mk_header_accessor( ContentDisposition, contentDisposition )
1005kmime_mk_header_accessor( ContentDescription, contentDescription )
1006kmime_mk_header_accessor( ContentLocation, contentLocation )
1007kmime_mk_header_accessor( ContentID, contentID )
1008
1009#undef kmime_mk_header_accessor
1010// @endcond
1011
1012
1013void ContentPrivate::clearBodyMessage()
1014{
1015 bodyAsMessage.reset();
1016}
1017
1018Content::List ContentPrivate::contents() const
1019{
1020 Q_ASSERT( multipartContents.isEmpty() || !bodyAsMessage );
1021 if ( bodyAsMessage )
1022 return Content::List() << bodyAsMessage.get();
1023 else
1024 return multipartContents;
1025}
1026
1027bool ContentPrivate::parseUuencoded()
1028{
1029 Q_Q( Content );
1030 Parser::UUEncoded uup( body, KMime::extractHeader( head, "Subject" ) );
1031 if( !uup.parse() ) {
1032 return false; // Parsing failed.
1033 }
1034
1035 Headers::ContentType *ct = q->contentType();
1036 ct->clear();
1037
1038 if( uup.isPartial() ) {
1039 // This seems to be only a part of the message, so we treat it as "message/partial".
1040 ct->setMimeType( "message/partial" );
1041 //ct->setId( uniqueString() ); not needed yet
1042 ct->setPartialParams( uup.partialCount(), uup.partialNumber() );
1043 q->contentTransferEncoding()->setEncoding( Headers::CE7Bit );
1044 } else {
1045 // This is a complete message, so treat it as "multipart/mixed".
1046 body.clear();
1047 ct->setMimeType( "multipart/mixed" );
1048 ct->setBoundary( multiPartBoundary() );
1049 ct->setCategory( Headers::CCcontainer );
1050 q->contentTransferEncoding()->clear(); // 7Bit, decoded.
1051
1052 // Add the plain text part first.
1053 Q_ASSERT( multipartContents.count() == 0 );
1054 {
1055 Content *c = new Content( q );
1056 c->contentType()->setMimeType( "text/plain" );
1057 c->contentTransferEncoding()->setEncoding( Headers::CE7Bit );
1058 c->setBody( uup.textPart() );
1059 multipartContents.append( c );
1060 }
1061
1062 // Now add each of the binary parts as sub-Contents.
1063 for( int i = 0; i < uup.binaryParts().count(); ++i ) {
1064 Content *c = new Content( q );
1065 c->contentType()->setMimeType( uup.mimeTypes().at( i ) );
1066 c->contentType()->setName( QLatin1String( uup.filenames().at( i ) ), QByteArray( /*charset*/ ) );
1067 c->contentTransferEncoding()->setEncoding( Headers::CEuuenc );
1068 c->contentTransferEncoding()->setDecoded( false );
1069 c->contentDisposition()->setDisposition( Headers::CDattachment );
1070 c->contentDisposition()->setFilename( QLatin1String( uup.filenames().at( i ) ) );
1071 c->setBody( uup.binaryParts().at( i ) );
1072 c->changeEncoding( Headers::CEbase64 ); // Convert to base64.
1073 multipartContents.append( c );
1074 }
1075 }
1076
1077 return true; // Parsing successful.
1078}
1079
1080bool ContentPrivate::parseYenc()
1081{
1082 Q_Q( Content );
1083 Parser::YENCEncoded yenc( body );
1084 if ( !yenc.parse() ) {
1085 return false; // Parsing failed.
1086 }
1087
1088 Headers::ContentType *ct = q->contentType();
1089 ct->clear();
1090
1091 if ( yenc.isPartial() ) {
1092 // Assume there is exactly one decoded part. Treat this as "message/partial".
1093 ct->setMimeType( "message/partial" );
1094 //ct->setId( uniqueString() ); not needed yet
1095 ct->setPartialParams( yenc.partialCount(), yenc.partialNumber() );
1096 q->contentTransferEncoding()->setEncoding( Headers::CEbinary );
1097 q->changeEncoding( Headers::CEbase64 ); // Convert to base64.
1098 } else {
1099 // This is a complete message, so treat it as "multipart/mixed".
1100 body.clear();
1101 ct->setMimeType( "multipart/mixed" );
1102 ct->setBoundary( multiPartBoundary() );
1103 ct->setCategory( Headers::CCcontainer );
1104 q->contentTransferEncoding()->clear(); // 7Bit, decoded.
1105
1106 // Add the plain text part first.
1107 Q_ASSERT( multipartContents.count() == 0 );
1108 {
1109 Content *c = new Content( q );
1110 c->contentType()->setMimeType( "text/plain" );
1111 c->contentTransferEncoding()->setEncoding( Headers::CE7Bit );
1112 c->setBody( yenc.textPart() );
1113 multipartContents.append( c );
1114 }
1115
1116 // Now add each of the binary parts as sub-Contents.
1117 for ( int i=0; i<yenc.binaryParts().count(); i++ ) {
1118 Content *c = new Content( q );
1119 c->contentType()->setMimeType( yenc.mimeTypes().at( i ) );
1120 c->contentType()->setName( QLatin1String( yenc.filenames().at( i ) ), QByteArray( /*charset*/ ) );
1121 c->contentTransferEncoding()->setEncoding( Headers::CEbinary );
1122 c->contentDisposition()->setDisposition( Headers::CDattachment );
1123 c->contentDisposition()->setFilename( QLatin1String( yenc.filenames().at( i ) ) );
1124 c->setBody( yenc.binaryParts().at( i ) ); // Yenc bodies are binary.
1125 c->changeEncoding( Headers::CEbase64 ); // Convert to base64.
1126 multipartContents.append( c );
1127 }
1128 }
1129
1130 return true; // Parsing successful.
1131}
1132
1133bool ContentPrivate::parseMultipart()
1134{
1135 Q_Q( Content );
1136 const Headers::ContentType *ct = q->contentType();
1137 const QByteArray boundary = ct->boundary();
1138 if ( boundary.isEmpty() ) {
1139 return false; // Parsing failed; invalid multipart content.
1140 }
1141 Parser::MultiPart mpp( body, boundary );
1142 if ( !mpp.parse() ) {
1143 return false; // Parsing failed.
1144 }
1145
1146 preamble = mpp.preamble();
1147 epilogue = mpp.epilouge();
1148
1149 // Determine the category of the subparts (used in attachments()).
1150 Headers::contentCategory cat;
1151 if ( ct->isSubtype( "alternative" ) ) {
1152 cat = Headers::CCalternativePart;
1153 } else {
1154 cat = Headers::CCmixedPart; // Default to "mixed".
1155 }
1156
1157 // Create a sub-Content for every part.
1158 Q_ASSERT( multipartContents.isEmpty() );
1159 body.clear();
1160 QList<QByteArray> parts = mpp.parts();
1161 foreach ( const QByteArray &part, mpp.parts() ) {
1162 Content *c = new Content( q );
1163 c->setContent( part );
1164 c->setFrozen( frozen );
1165 c->parse();
1166 c->contentType()->setCategory( cat );
1167 multipartContents.append( c );
1168 }
1169
1170 return true; // Parsing successful.
1171}
1172
1173} // namespace KMime
1174