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 | |
57 | using namespace KMime; |
58 | |
59 | namespace KMime { |
60 | |
61 | Content::Content() |
62 | : d_ptr( new ContentPrivate( this ) ) |
63 | { |
64 | } |
65 | |
66 | Content::Content( Content *parent ) |
67 | : d_ptr( new ContentPrivate( this ) ) |
68 | { |
69 | d_ptr->parent = parent; |
70 | } |
71 | |
72 | Content::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 | |
79 | Content::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 | |
87 | Content::Content( ContentPrivate *d ) |
88 | : d_ptr( d ) |
89 | { |
90 | } |
91 | |
92 | Content::~Content() |
93 | { |
94 | qDeleteAll( h_eaders ); |
95 | h_eaders.clear(); |
96 | delete d_ptr; |
97 | d_ptr = 0; |
98 | } |
99 | |
100 | bool Content::hasContent() const |
101 | { |
102 | return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents().isEmpty(); |
103 | } |
104 | |
105 | void 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 | |
134 | void Content::setContent( const QByteArray &s ) |
135 | { |
136 | Q_D( Content ); |
137 | KMime::HeaderParsing::extractHeaderAndBody( s, d->head, d->body ); |
138 | } |
139 | |
140 | QByteArray Content::head() const |
141 | { |
142 | return d_ptr->head; |
143 | } |
144 | |
145 | void Content::( const QByteArray &head ) |
146 | { |
147 | d_ptr->head = head; |
148 | if ( !head.endsWith( '\n' ) ) { |
149 | d_ptr->head += '\n'; |
150 | } |
151 | } |
152 | |
153 | QByteArray Content::body() const |
154 | { |
155 | return d_ptr->body; |
156 | } |
157 | |
158 | void Content::setBody( const QByteArray &body ) |
159 | { |
160 | d_ptr->body = body; |
161 | } |
162 | |
163 | QByteArray Content::preamble() const |
164 | { |
165 | return d_ptr->preamble; |
166 | } |
167 | |
168 | void Content::setPreamble( const QByteArray &preamble ) |
169 | { |
170 | d_ptr->preamble = preamble; |
171 | } |
172 | |
173 | |
174 | QByteArray Content::epilogue() const |
175 | { |
176 | return d_ptr->epilogue; |
177 | } |
178 | |
179 | void Content::setEpilogue( const QByteArray &epilogue ) |
180 | { |
181 | d_ptr->epilogue = epilogue; |
182 | } |
183 | |
184 | void 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 | |
244 | bool Content::isFrozen() const |
245 | { |
246 | return d_ptr->frozen; |
247 | } |
248 | |
249 | void Content::setFrozen( bool frozen ) |
250 | { |
251 | d_ptr->frozen = frozen; |
252 | } |
253 | |
254 | void 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 | |
267 | QByteArray Content::() |
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 | |
279 | void 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 | |
289 | void 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 | |
299 | QByteArray 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 | |
316 | QByteArray 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 | |
375 | QByteArray 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 | |
427 | QString 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 | |
468 | void 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 | |
484 | Content *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 | |
501 | Content::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 | |
526 | Content::List Content::contents() const |
527 | { |
528 | return d_ptr->contents(); |
529 | } |
530 | |
531 | void 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 | |
589 | void 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 | |
627 | void Content::( 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 | |
657 | void 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 | |
670 | Headers::Generic *Content::( QByteArray &head ) |
671 | { |
672 | return d_ptr->nextHeader( head ); |
673 | } |
674 | |
675 | Headers::Generic *Content::( QByteArray &head ) |
676 | { |
677 | return d_ptr->nextHeader( head ); |
678 | } |
679 | |
680 | Headers::Generic *ContentPrivate::( QByteArray &_head ) |
681 | { |
682 | Headers::Base * = 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 | |
692 | Headers::Base *Content::( const char *type ) |
693 | { |
694 | return headerByType( type ); |
695 | } |
696 | |
697 | Headers::Base *Content::( 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 | |
710 | Headers::Base::List Content::( 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 | |
725 | void Content::( Headers::Base *h ) |
726 | { |
727 | Q_ASSERT( h ); |
728 | removeHeader( h->type() ); |
729 | appendHeader( h ); |
730 | } |
731 | |
732 | void Content::( Headers::Base *h ) |
733 | { |
734 | h_eaders.append( h ); |
735 | h->setParent( this ); |
736 | } |
737 | |
738 | void Content::( Headers::Base *h ) |
739 | { |
740 | h_eaders.prepend( h ); |
741 | h->setParent( this ); |
742 | } |
743 | |
744 | bool Content::( 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 | |
757 | bool Content::( const char *type ) |
758 | { |
759 | return headerByType( type ) != 0; |
760 | } |
761 | |
762 | int 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 | |
778 | int 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 | |
797 | int 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 | |
813 | QByteArray Content::( const char *name ) const |
814 | { |
815 | return KMime::extractHeader( d_ptr->head, name ); |
816 | } |
817 | |
818 | QList<QByteArray> Content::( const char *name ) const |
819 | { |
820 | return KMime::extractHeaders( d_ptr->head, name ); |
821 | } |
822 | |
823 | bool 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 | |
858 | QByteArray Content::defaultCharset() const |
859 | { |
860 | return d_ptr->defaultCS; |
861 | } |
862 | |
863 | void 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 | |
876 | bool Content::forceDefaultCharset() const |
877 | { |
878 | return d_ptr->forceDefaultCS; |
879 | } |
880 | |
881 | void 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 | |
894 | Content * 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 | |
908 | ContentIndex 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 | |
928 | bool Content::isTopLevel() const |
929 | { |
930 | return d_ptr->parent == 0; |
931 | } |
932 | |
933 | void 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 | |
951 | Content *Content::parent() const |
952 | { |
953 | return d_ptr->parent; |
954 | } |
955 | |
956 | Content *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 | |
968 | ContentIndex 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 | |
978 | Message::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 | |
987 | bool 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 ) \ |
998 | Headers::type *Content::method( bool create ) { \ |
999 | return header<Headers::type>( create ); \ |
1000 | } |
1001 | |
1002 | kmime_mk_header_accessor( ContentType, contentType ) |
1003 | kmime_mk_header_accessor( ContentTransferEncoding, contentTransferEncoding ) |
1004 | kmime_mk_header_accessor( ContentDisposition, contentDisposition ) |
1005 | kmime_mk_header_accessor( ContentDescription, contentDescription ) |
1006 | kmime_mk_header_accessor( ContentLocation, contentLocation ) |
1007 | kmime_mk_header_accessor( ContentID, contentID ) |
1008 | |
1009 | #undef kmime_mk_header_accessor |
1010 | // @endcond |
1011 | |
1012 | |
1013 | void ContentPrivate::clearBodyMessage() |
1014 | { |
1015 | bodyAsMessage.reset(); |
1016 | } |
1017 | |
1018 | Content::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 | |
1027 | bool 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 | |
1080 | bool 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 | |
1133 | bool 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 | |