KMime

content.cpp
Go to the documentation of this file.
1/*
2 KMime, the KDE Internet mail/usenet news message library.
3 SPDX-FileCopyrightText: 2001 the KMime authors.
4 See file AUTHORS for details
5 SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
6 SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
7
8 SPDX-License-Identifier: LGPL-2.0-or-later
9*/
10/**
11 @file
12 This file is part of the API for handling @ref MIME data and
13 defines the Content class.
14
15 @brief
16 Defines the Content class.
17
18 @authors the KMime authors (see AUTHORS file),
19 Volker Krause <vkrause@kde.org>
20*/
21#include "content.h"
22#include "content_p.h"
23#include "message.h"
24#include "headerparsing.h"
25#include "headerparsing_p.h"
26#include "parsers_p.h"
27#include "util_p.h"
28
29#include <KCodecs>
30
31#include <QDebug>
32#include <QStringDecoder>
33#include <QStringEncoder>
34
35using namespace KMime;
36
37namespace KMime
38{
39
41 : d_ptr(new ContentPrivate)
42{
43 d_ptr->parent = parent;
44}
45
47{
48 Q_D(Content);
49 qDeleteAll(d->headers);
50 d->headers.clear();
51 delete d_ptr;
52 d_ptr = nullptr;
53}
54
56{
57 return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents().isEmpty();
58}
59
61{
62 Q_D(Content);
63 KMime::HeaderParsing::extractHeaderAndBody(s, d->head, d->body);
64}
65
67{
68 return d_ptr->head;
69}
70
72{
73 d_ptr->head = head;
74 if (!head.endsWith('\n')) {
75 d_ptr->head += '\n';
76 }
77}
78
80{
81 return d_ptr->body;
82}
83
85{
86 d_ptr->body = body;
87 d_ptr->m_decoded = true;
88}
89
91{
92 d_ptr->body = body;
93 d_ptr->m_decoded = false;
94}
95
97{
98 return d_ptr->preamble;
99}
100
101void Content::setPreamble(const QByteArray &preamble)
102{
103 d_ptr->preamble = preamble;
104}
105
107{
108 return d_ptr->epilogue;
109}
110
111void Content::setEpilogue(const QByteArray &epilogue)
112{
113 d_ptr->epilogue = epilogue;
114}
115
117{
118 Q_D(Content);
119
120 // Clean up old headers and parse them again.
121 qDeleteAll(d->headers);
122 d->headers.clear();
123 d->headers = HeaderParsing::parseHeaders(d->head);
124 if (const auto cte = contentTransferEncoding(false); cte) {
125 d->m_decoded = (cte->encoding() == Headers::CE7Bit || cte->encoding() == Headers::CE8Bit);
126 }
127
128 // If we are frozen, save the body as-is. This is done because parsing
129 // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.)
130 if (d->frozen) {
131 d->frozenBody = d->body;
132 }
133
134 // Clean up old sub-Contents and parse them again.
135 qDeleteAll(d->multipartContents);
136 d->multipartContents.clear();
137 d->clearBodyMessage();
139 if (ct->isEmpty()) { //Set default content-type as defined in https://tools.ietf.org/html/rfc2045#page-10 (5.2. Content-Type Defaults)
140 ct->setMimeType("text/plain");
141 ct->setCharset("us-ascii");
142 }
143 if (ct->isText()) {
144 // This content is either text, or of unknown type.
145
146 if (d->parseUuencoded(this)) {
147 // This is actually uuencoded content generated by broken software.
148 } else if (d->parseYenc(this)) {
149 // This is actually yenc content generated by broken software.
150 } else {
151 // This is just plain text.
152 }
153 } else if (ct->isMultipart()) {
154 // This content claims to be MIME multipart.
155
156 if (d->parseMultipart(this)) {
157 // This is actual MIME multipart content.
158 } else {
159 // Parsing failed; treat this content as "text/plain".
160 ct->setMimeType("text/plain");
161 ct->setCharset("US-ASCII");
162 }
163 } else {
164 // This content is something else, like an encapsulated message or a binary attachment
165 // or something like that
166 if (bodyIsMessage()) {
167 d->bodyAsMessage = Message::Ptr(new Message);
168 d->bodyAsMessage->setContent(d->body);
169 d->bodyAsMessage->setFrozen(d->frozen);
170 d->bodyAsMessage->parse();
171 d->bodyAsMessage->d_ptr->parent = this;
172
173 // Clear the body, as it is now represented by d->bodyAsMessage. This is the same behavior
174 // as with multipart contents, since parseMultipart() clears the body as well
175 d->body.clear();
176 }
177 }
178}
179
181{
182 return d_ptr->frozen;
183}
184
185void Content::setFrozen(bool frozen)
186{
187 d_ptr->frozen = frozen;
188}
189
191{
192 Q_D(Content);
193 if (d->frozen) {
194 return;
195 }
196
197 d->head = assembleHeaders();
198 const auto contentsList = contents();
199 for (Content *c : contentsList) {
200 c->assemble();
201 }
202}
203
205{
206 Q_D(Content);
207 QByteArray newHead;
208 for (const Headers::Base *h : std::as_const(d->headers)) {
209 if (!h->isEmpty()) {
210 newHead += foldHeader(h->as7BitString()) + '\n';
211 }
212 }
213
214 return newHead;
215}
216
218{
219 Q_D(Content);
220 qDeleteAll(d->headers);
221 d->headers.clear();
223 d->head.clear();
224 d->body.clear();
225}
226
228{
229 Q_D(Content);
230 if (del) {
231 qDeleteAll(d->multipartContents);
232 }
233 d->multipartContents.clear();
234 d->clearBodyMessage();
235}
236
238{
239 QByteArray encodedContentData = head(); // return value; initialize with the head data
240 const QByteArray encodedBodyData = encodedBody();
241
242 /* Make sure that head and body have at least two newlines as separator, otherwise add one.
243 * If we have enough newlines as separator, then we should not change the number of newlines
244 * to not break digital signatures
245 */
246 if (!encodedContentData.endsWith("\n\n") &&
247 !encodedBodyData.startsWith("\n\n") &&
248 !(encodedContentData.endsWith("\n") && encodedBodyData.startsWith("\n"))){
249 encodedContentData += '\n';
250 }
251 encodedContentData += encodedBodyData;
252
253 if (useCrLf) {
254 return LFtoCRLF(encodedContentData);
255 } else {
256 return encodedContentData;
257 }
258}
259
261{
262 Q_D(const Content);
263 QByteArray e;
264 // Body.
265 if (d->frozen) {
266 // This Content is frozen.
267 if (d->frozenBody.isEmpty()) {
268 // This Content has never been parsed.
269 e += d->body;
270 } else {
271 // Use the body as it was before parsing.
272 e += d->frozenBody;
273 }
274 } else if (bodyIsMessage() && d->bodyAsMessage) {
275 // This is an encapsulated message
276 // No encoding needed, as the ContentTransferEncoding can only be 7bit
277 // for encapsulated messages
278 e += d->bodyAsMessage->encodedContent();
279 } else if (!d->body.isEmpty()) {
280 // This is a single-part Content.
281 const auto enc = contentTransferEncoding();
282
283 if (enc && d->needToEncode(this)) {
284 if (enc->encoding() == Headers::CEquPr) {
285 e += KCodecs::quotedPrintableEncode(d->body, false);
286 } else {
287 QByteArray encoded;
288 KCodecs::base64Encode(d->body, encoded, true);
289 e += encoded;
290 e += '\n';
291 }
292 } else {
293 e += d->body;
294 }
295 }
296
297 if (!d->frozen && !d->multipartContents.isEmpty()) {
298 // This is a multipart Content.
299 const auto ct = contentType();
300 QByteArray boundary = "\n--" + (ct ? ct->boundary() : QByteArray());
301
302 if (!d->preamble.isEmpty()) {
303 e += d->preamble;
304 }
305
306 //add all (encoded) contents separated by boundaries
307 for (Content *c : std::as_const(d->multipartContents)) {
308 e += boundary + '\n';
309 e += c->encodedContent(false); // don't convert LFs here, we do that later!!!!!
310 }
311 //finally append the closing boundary
312 e += boundary + "--\n";
313
314 if (!d->epilogue.isEmpty()) {
315 e += d->epilogue;
316 }
317 }
318 return e;
319}
320
322{
323 QByteArray ret;
325 bool removeTrailingNewline = false;
326
327 if (d_ptr->body.isEmpty()) {
328 return ret;
329 }
330
331 if (!ec || d_ptr->m_decoded) {
332 ret = d_ptr->body;
333 //Laurent Fix bug #311267
334 //removeTrailingNewline = true;
335 } else {
336 switch (ec->encoding()) {
337 case Headers::CEbase64 : {
339 Q_ASSERT(codec);
340 ret.resize(codec->maxDecodedSizeFor(d_ptr->body.size()));
342 QByteArray::const_iterator inputIt = d_ptr->body.constBegin();
343 QByteArray::iterator resultIt = ret.begin();
344 decoder->decode(inputIt, d_ptr->body.constEnd(), resultIt, ret.constEnd());
345 ret.truncate(resultIt - ret.begin());
346 break;
347 }
348 case Headers::CEquPr :
349 ret = KCodecs::quotedPrintableDecode(d_ptr->body);
350 removeTrailingNewline = true;
351 break;
352 case Headers::CEuuenc :
353 KCodecs::uudecode(d_ptr->body, ret);
354 break;
355 case Headers::CEbinary :
356 ret = d_ptr->body;
357 removeTrailingNewline = false;
358 break;
359 default :
360 ret = d_ptr->body;
361 removeTrailingNewline = true;
362 }
363 }
364
365 if (removeTrailingNewline && (ret.size() > 0) && (ret[ret.size() - 1] == '\n')) {
366 ret.resize(ret.size() - 1);
367 }
368
369 return ret;
370}
371
372QString Content::decodedText(bool trimText, bool removeTrailingNewlines) const
373{
374 if (!d_ptr->decodeText(this)) { //this is not a text content !!
375 return {};
376 }
377
378 QStringDecoder codec;
379 if (const auto ct = contentType(); ct) {
380 codec = QStringDecoder(ct->charset().constData());
381 }
382 if (!codec.isValid()) { // no suitable codec found => try local settings and hope for the best ;-)
384 }
385
386 QString s = codec.decode(d_ptr->body);
387
388 if (trimText || removeTrailingNewlines) {
389 qsizetype i;
390 for (i = s.length() - 1; i >= 0; --i) {
391 if (trimText) {
392 if (!s[i].isSpace()) {
393 break;
394 }
395 } else {
396 if (s[i] != QLatin1Char('\n')) {
397 break;
398 }
399 }
400 }
401 s.truncate(i + 1);
402 } else {
403 if (s.endsWith(QLatin1Char('\n'))) {
404 s.chop(1); // remove trailing new-line
405 }
406 }
407
408 return s;
409}
410
412{
413 QStringEncoder codec(contentType()->charset().constData());
414
415 if (!codec.isValid()) { // no suitable codec found => try local settings and hope for the best ;-)
417 QByteArray chset = codec.name();
418 contentType()->setCharset(chset);
419 }
420
421 d_ptr->body = codec.encode(s);
422 d_ptr->m_decoded = true; //text is always decoded
423}
424
426{
427 // we start from a non-const this we know the result will be safely const_castable as well
428 return const_cast<Content*>(static_cast<const Content*>(this)->textContent());
429}
430
432{
433 //return the first content with mimetype=text/*
434 // see ContentType::isText, that's also true for an empty header
435 if (const auto ct = contentType(); !ct || ct->isText()) {
436 return this;
437 }
438
439 const auto contents = d_ptr->contents();
440 for (const Content *c : contents) {
441 if (auto ret = c->textContent()) {
442 return ret;
443 }
444 }
445
446 return nullptr;
447}
448
450 QList<Content *> result;
451
452 auto ct = contentType();
453 if (ct && ct->isMultipart() &&
454 !ct->isSubtype("related") /* && !ct->isSubtype("alternative")*/) {
455 const QList<Content *> contentsList = contents();
456 result.reserve(contentsList.count());
457 for (Content *child : contentsList) {
458 if (isAttachment(child)) {
459 result.push_back(child);
460 } else {
461 result += child->attachments();
462 }
463 }
464 }
465
466 return result;
467}
468
469QList<Content *> Content::contents() const { return d_ptr->contents(); }
470
471void Content::replaceContent(Content *oldContent, Content *newContent)
472{
473 Q_D( Content );
474 if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( oldContent ) ) {
475 return;
476 }
477
478 d->multipartContents.removeAll( oldContent );
479 delete oldContent;
480 d->multipartContents.append( newContent );
481 if( newContent->parent() != this ) {
482 // If the content was part of something else, this will remove it from there.
483 newContent->setParent( this );
484 }
485}
486
487
489{
490 // This method makes no sense for encapsulated messages
491 Q_ASSERT(!bodyIsMessage());
492
493 Q_D(Content);
494 d->multipartContents.append(c);
495
496 if (c->parent() != this) {
497 // If the content was part of something else, this will remove it from there.
498 c->setParent(this);
499 }
500}
501
503{
504 // This method makes no sense for encapsulated messages
505 Q_ASSERT(!bodyIsMessage());
506
507 Q_D(Content);
508 d->multipartContents.prepend(c);
509
510 if (c->parent() != this) {
511 // If the content was part of something else, this will remove it from there.
512 c->setParent(this);
513 }
514}
515
517{
518 // This method makes no sense for encapsulated messages.
519 // Should be covered by the above assert already, though.
520 Q_ASSERT(!bodyIsMessage());
521
522 Q_D(Content);
523 if (d->multipartContents.isEmpty() || !d->multipartContents.contains(c)) {
524 return nullptr;
525 }
526
527 d->multipartContents.removeAll(c);
528 c->d_ptr->parent = nullptr;
529 return c;
530}
531
533{
534 // This method makes no sense for encapsulated messages, they are always 7bit
535 // encoded.
536 Q_ASSERT(!bodyIsMessage());
537
539 if (enc->encoding() == e) {
540 // Nothing to do.
541 return;
542 }
543
544 if (d_ptr->decodeText(this)) {
545 // This is textual content. Textual content is stored decoded.
546 Q_ASSERT(d_ptr->m_decoded);
547 enc->setEncoding(e);
548 } else {
549 // This is non-textual content. Re-encode it.
550 if (e == Headers::CEbase64) {
551 KCodecs::base64Encode(decodedContent(), d_ptr->body, true);
552 enc->setEncoding(e);
553 d_ptr->m_decoded = false;
554 } else {
555 // It only makes sense to convert binary stuff to base64.
556 Q_ASSERT(false);
557 }
558 }
559}
560
561QList<Headers::Base *> Content::headers() const { return d_ptr->headers; }
562
563Headers::Base *Content::headerByType(const char *type) const
564{
565 Q_ASSERT(type && *type);
566
567 for (Headers::Base *h : std::as_const(d_ptr->headers)) {
568 if (h->is(type)) {
569 return h; // Found.
570 }
571 }
572
573 return nullptr; // Not found.
574}
575
577 Q_ASSERT(type && *type);
578
580
581 for (Headers::Base *h : std::as_const(d_ptr->headers)) {
582 if (h->is(type)) {
583 result << h;
584 }
585 }
586
587 return result;
588}
589
591{
592 Q_ASSERT(h);
593 removeHeader(h->type());
594 appendHeader(h);
595}
596
598{
599 Q_D(Content);
600 d->headers.append(h);
601}
602
603bool Content::removeHeader(const char *type)
604{
605 Q_D(Content);
606 const auto endIt = d->headers.end();
607 for (auto it = d->headers.begin(); it != endIt; ++it) {
608 if ((*it)->is(type)) {
609 delete(*it);
610 d->headers.erase(it);
611 return true;
612 }
613 }
614
615 return false;
616}
617
618bool Content::hasHeader(const char* type) const
619{
620 return headerByType(type) != nullptr;
621}
622
623qsizetype Content::size() const
624{
625 const auto ret = d_ptr->body.size();
626
627 if (const auto cte = contentTransferEncoding(); cte && cte->encoding() == Headers::CEbase64) {
629 return codec->maxEncodedSizeFor(ret);
630 }
631
632 // Not handling quoted-printable here since that requires actually
633 // converting the content, and that is O(size_of_content).
634 // For quoted-printable, this is only an approximate size.
635
636 return ret;
637}
638
639qsizetype Content::storageSize() const
640{
641 const Q_D(Content);
642 auto s = d->head.size();
643
644 if (d->contents().isEmpty()) {
645 s += d->body.size();
646 } else {
647
648 // FIXME: This should take into account the boundary headers that are added in
649 // encodedContent!
650 const auto contents = d->contents();
651 for (Content *c : contents) {
652 s += c->storageSize();
653 }
654 }
655
656 return s;
657}
658
659bool ContentPrivate::needToEncode(const Content *q) const
660{
661 const auto cte = q->contentTransferEncoding();
662 return m_decoded && cte && (cte->encoding() == Headers::CEquPr || cte->encoding() == Headers::CEbase64);
663}
664
665bool ContentPrivate::decodeText(const Content *q)
666{
668
669 if (const auto ct = q->contentType(); ct && !ct->isText()) {
670 return false; //non textual data cannot be decoded here => use decodedContent() instead
671 }
672 if (m_decoded) {
673 return true; //nothing to do
674 }
675
676 if (enc) {
677 switch (enc->encoding()) {
678 case Headers::CEbase64 :
679 body = KCodecs::base64Decode(body);
680 break;
681 case Headers::CEquPr :
683 break;
684 case Headers::CEuuenc :
685 body = KCodecs::uudecode(body);
686 break;
687 case Headers::CEbinary :
688 // nothing to decode
689 default :
690 break;
691 }
692 }
693 if (!body.endsWith('\n')) {
694 body.append('\n');
695 }
696 m_decoded = true;
697 return true;
698}
699
701{
702 if (!index.isValid()) {
703 return const_cast<KMime::Content *>(this);
704 }
705 ContentIndex idx = index;
706 unsigned int i = idx.pop() - 1; // one-based -> zero-based index
707 if (i < static_cast<unsigned int>(d_ptr->contents().size())) {
708 return d_ptr->contents().at(i)->content(idx);
709 } else {
710 return nullptr;
711 }
712}
713
715{
716 const auto i = d_ptr->contents().indexOf(content);
717 if (i >= 0) {
718 ContentIndex ci;
719 ci.push(i + 1); // zero-based -> one-based index
720 return ci;
721 }
722 // not found, we need to search recursively
723 for (int i = 0; i < d_ptr->contents().size(); ++i) {
724 ContentIndex ci = d_ptr->contents().at(i)->indexForContent(content);
725 if (ci.isValid()) {
726 // found it
727 ci.push(i + 1); // zero-based -> one-based index
728 return ci;
729 }
730 }
731 return {}; // not found
732}
733
735{
736 return d_ptr->parent == nullptr;
737}
738
740{
741 // Make sure the Content is only in the contents list of one parent object
742 Content *oldParent = d_ptr->parent;
743 if (oldParent) {
744 if (!oldParent->contents().isEmpty() && oldParent->contents().contains(this)) {
745 oldParent->takeContent(this);
746 }
747 }
748
749 d_ptr->parent = parent;
750 if (parent) {
751 if (!parent->contents().isEmpty() && !parent->contents().contains(this)) {
752 parent->appendContent(this);
753 }
754 }
755}
756
758{
759 return d_ptr->parent;
760}
761
762const Content *Content::parent() const
763{
764 return d_ptr->parent;
765}
766
768{
769 auto c = this;
770 while (c->parent()) {
771 c = c->parent();
772 }
773 return c;
774}
775
776const Content *Content::topLevel() const
777{
778 auto c = this;
779 while (c->parent()) {
780 c = c->parent();
781 }
782 return c;
783}
784
786{
787 auto top = topLevel();
788 if (top) {
789 return top->indexForContent(const_cast<Content *>(this));
790 }
791
792 return indexForContent(const_cast<Content *>(this));
793}
794
796{
797 if (bodyIsMessage() && d_ptr->bodyAsMessage) {
798 return d_ptr->bodyAsMessage;
799 } else {
800 return {};
801 }
802}
803
805{
806 if (const auto ct = contentType(); ct) {
807 return ct->isMimeType("message/rfc822");
808 }
809 return false;
810}
811
812// @cond PRIVATE
813#define kmime_mk_header_accessor( type, method ) \
814 Headers::type *Content::method( bool create ) { \
815 return header<Headers::type>( create ); \
816 } \
817 const Headers::type *Content::method() const { \
818 return header<Headers::type>(); \
819 }
820
821kmime_mk_header_accessor(ContentType, contentType)
822kmime_mk_header_accessor(ContentTransferEncoding, contentTransferEncoding)
823kmime_mk_header_accessor(ContentDisposition, contentDisposition)
824kmime_mk_header_accessor(ContentDescription, contentDescription)
825kmime_mk_header_accessor(ContentLocation, contentLocation)
826kmime_mk_header_accessor(ContentID, contentID)
827
828#undef kmime_mk_header_accessor
829// @endcond
830
831void ContentPrivate::clearBodyMessage()
832{
833 bodyAsMessage.reset();
834}
835
836QList<Content *> ContentPrivate::contents() const {
837 Q_ASSERT(multipartContents.isEmpty() || !bodyAsMessage);
838 if (bodyAsMessage) {
839 return QList<Content *>() << bodyAsMessage.data();
840 } else {
841 return multipartContents;
842 }
843}
844
845bool ContentPrivate::parseUuencoded(Content *q)
846{
847 Parser::UUEncoded uup(body, head);
848 if (!uup.parse()) {
849 return false; // Parsing failed.
850 }
851
853 ct->clear();
854
855 if (uup.isPartial()) {
856 // This seems to be only a part of the message, so we treat it as "message/partial".
857 ct->setMimeType("message/partial");
858 //ct->setId( uniqueString() ); not needed yet
859 ct->setPartialParams(uup.partialCount(), uup.partialNumber());
860 q->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
861 } else {
862 // This is a complete message, so treat it as "multipart/mixed".
863 const auto prevBody = body;
864 body.clear();
865 ct->setMimeType("multipart/mixed");
866 ct->setBoundary(multiPartBoundary());
867 auto cte = q->contentTransferEncoding();
868 cte->setEncoding(Headers::CE7Bit);
869 m_decoded = true;
870
871 // Add the plain text part first.
872 Q_ASSERT(multipartContents.isEmpty());
873 {
874 auto c = new Content(q);
875 c->contentType()->setMimeType("text/plain");
876 c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
877 c->setBody(uup.textPart());
878 multipartContents.append(c);
879 }
880
881 // Now add each of the binary parts as sub-Contents.
882 for (int i = 0; i < uup.binaryParts().count(); ++i) {
883 auto c = new Content(q);
884 c->contentType()->setMimeType(uup.mimeTypes().at(i));
885 c->contentType()->setName(QLatin1StringView(uup.filenames().at(i)));
886 c->contentTransferEncoding()->setEncoding(Headers::CEuuenc);
887 c->contentDisposition()->setDisposition(Headers::CDattachment);
888 c->contentDisposition()->setFilename(
889 QLatin1StringView(uup.filenames().at(i)));
890 // uup.binaryParts().at(i) does no longer have the uuencode header, which makes KCodecs fail since 5c66308c4786ef7fbf77b0e306e73f7d4ac3431b
891 c->setEncodedBody(prevBody);
892 c->changeEncoding(Headers::CEbase64); // Convert to base64.
893 multipartContents.append(c);
894 }
895 }
896
897 return true; // Parsing successful.
898}
899
900bool ContentPrivate::parseYenc(Content *q)
901{
902 Parser::YENCEncoded yenc(body);
903 if (!yenc.parse()) {
904 return false; // Parsing failed.
905 }
906
908 ct->clear();
909
910 if (yenc.isPartial()) {
911 // Assume there is exactly one decoded part. Treat this as "message/partial".
912 ct->setMimeType("message/partial");
913 //ct->setId( uniqueString() ); not needed yet
914 ct->setPartialParams(yenc.partialCount(), yenc.partialNumber());
915 q->contentTransferEncoding()->setEncoding(Headers::CEbinary);
916 q->changeEncoding(Headers::CEbase64); // Convert to base64.
917 } else {
918 // This is a complete message, so treat it as "multipart/mixed".
919 body.clear();
920 ct->setMimeType("multipart/mixed");
921 ct->setBoundary(multiPartBoundary());
922 auto cte = q->contentTransferEncoding();
923 cte->setEncoding(Headers::CE7Bit);
924 m_decoded = true;
925
926 // Add the plain text part first.
927 Q_ASSERT(multipartContents.isEmpty());
928 {
929 auto c = new Content(q);
930 c->contentType()->setMimeType("text/plain");
931 c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
932 c->setBody(yenc.textPart());
933 multipartContents.append(c);
934 }
935
936 // Now add each of the binary parts as sub-Contents.
937 for (int i = 0; i < yenc.binaryParts().count(); i++) {
938 auto c = new Content(q);
939 c->contentType()->setMimeType(yenc.mimeTypes().at(i));
940 c->contentType()->setName(QLatin1StringView(yenc.filenames().at(i)));
941 c->contentTransferEncoding()->setEncoding(Headers::CEbinary);
942 c->contentDisposition()->setDisposition(Headers::CDattachment);
943 c->contentDisposition()->setFilename(
944 QLatin1StringView(yenc.filenames().at(i)));
945 c->setBody(yenc.binaryParts().at(i)); // Yenc bodies are binary.
946 c->changeEncoding(Headers::CEbase64); // Convert to base64.
947 multipartContents.append(c);
948 }
949 }
950
951 return true; // Parsing successful.
952}
953
954bool ContentPrivate::parseMultipart(Content *q)
955{
956 const Headers::ContentType *ct = q->contentType();
957 const QByteArray boundary = ct->boundary();
958 if (boundary.isEmpty()) {
959 return false; // Parsing failed; invalid multipart content.
960 }
961 Parser::MultiPart mpp(body, boundary);
962 if (!mpp.parse()) {
963 return false; // Parsing failed.
964 }
965
966 preamble = mpp.preamble();
967 epilogue = mpp.epilouge();
968
969 // Create a sub-Content for every part.
970 Q_ASSERT(multipartContents.isEmpty());
971 body.clear();
972 const auto parts = mpp.parts();
973 for (const QByteArray &part : parts) {
974 auto c = new Content(q);
975 c->setContent(part);
976 c->setFrozen(frozen);
977 c->parse();
978 multipartContents.append(c);
979 }
980
981 return true; // Parsing successful.
982}
983
984} // namespace KMime
virtual qsizetype maxEncodedSizeFor(qsizetype insize, NewlineType newline=NewlineLF) const=0
static Codec * codecForName(QByteArrayView name)
virtual Decoder * makeDecoder(NewlineType newline=NewlineLF) const=0
virtual qsizetype maxDecodedSizeFor(qsizetype insize, NewlineType newline=NewlineLF) const=0
A class to uniquely identify message parts (Content) in a hierarchy.
bool isValid() const
Returns true if this index is non-empty (valid).
unsigned int pop()
Removes and returns the top-most index.
void push(unsigned int index)
Adds index to the content index.
A class that encapsulates MIME encoded Content.
Definition content.h:90
bool hasContent() const
Returns true if this Content object is not empty.
Definition content.cpp:55
bool removeHeader()
Searches for the first header of type T, and deletes it, removing it from this Content.
Definition content.h:764
Headers::ContentType * contentType(bool create=true)
Returns the Content-Type header.
QList< Content * > attachments() const
Returns all attachments below this node, recursively.
Definition content.cpp:449
const Headers::ContentType * contentType() const
Returns the Content-Type header.
void setHeader(Headers::Base *h)
Sets the specified header to this Content.
Definition content.cpp:590
QByteArray encodedBody() const
Like encodedContent(), with the difference that only the body will be returned, i....
Definition content.cpp:260
void clearContents(bool del=true)
Removes all sub-Contents from this content.
Definition content.cpp:227
bool isFrozen() const
Returns whether this Content is frozen.
Definition content.cpp:180
Content * takeContent(Content *content)
Removes the given sub-Content and, if that actually was a sub-content returns that.
Definition content.cpp:516
ContentIndex index() const
Returns the index of this Content based on the topLevel() object.
Definition content.cpp:785
void setEpilogue(const QByteArray &epilogue)
Sets the MIME preamble.
Definition content.cpp:111
QByteArray epilogue() const
Returns the MIME preamble.
Definition content.cpp:106
Headers::ContentTransferEncoding * contentTransferEncoding(bool create=true)
Returns the Content-Transfer-Encoding header.
Content * parent()
Returns the parent content object, or 0 if the content doesn't have a parent.
Definition content.cpp:757
void setEncodedBody(const QByteArray &body)
Sets the Content body raw data encoded according to the content transfer encoding.
Definition content.cpp:90
QByteArray head() const
Returns the Content header raw data.
Definition content.cpp:66
QSharedPointer< Message > bodyAsMessage() const
If this content is an encapsulated message, in which case bodyIsMessage() will return true,...
Definition content.cpp:795
void assemble()
Generates the MIME content.
Definition content.cpp:190
virtual ~Content()
Destroys this Content object.
Definition content.cpp:46
Content * textContent()
Returns the first Content with mimetype text/.
Definition content.cpp:425
QByteArray decodedContent() const
Returns the decoded Content body.
Definition content.cpp:321
QList< Headers::Base * > headers() const
Returns all headers.
Definition content.cpp:561
Content * content(const ContentIndex &index) const
Returns the Content specified by the given index.
Definition content.cpp:700
qsizetype size() const
Returns the size of the Content body after encoding.
Definition content.cpp:623
void parse()
Parses the Content.
Definition content.cpp:116
QByteArray body() const
Returns the Content body raw data.
Definition content.cpp:79
bool isTopLevel() const
Returns true if this is the top-level node in the MIME tree.
Definition content.cpp:734
bool bodyIsMessage() const
Definition content.cpp:804
QString decodedText(bool trimText=false, bool removeTrailingNewlines=false) const
Returns the decoded text.
Definition content.cpp:372
QList< Headers::Base * > headersByType(const char *type) const
Returns all type headers in the Content.
Definition content.cpp:576
Content(Content *parent=nullptr)
Creates an empty Content object with a specified parent.
Definition content.cpp:40
const Headers::ContentTransferEncoding * contentTransferEncoding() const
Returns the Content-Transfer-Encoding header.
ContentIndex indexForContent(Content *content) const
Returns the ContentIndex for the given Content, or an invalid index if the Content is not found withi...
Definition content.cpp:714
void setContent(const QByteArray &s)
Sets the Content to the given raw data, containing the Content head and body separated by two linefee...
Definition content.cpp:60
void prependContent(Content *content)
Prepends a new sub-Content.
Definition content.cpp:502
Headers::Base * headerByType(const char *type) const
Returns the first header of type type, if it exists.
Definition content.cpp:563
virtual QByteArray assembleHeaders()
Reimplement this method if you need to assemble additional headers in a derived class.
Definition content.cpp:204
void setFrozen(bool frozen=true)
Freezes this Content if frozen is true; otherwise unfreezes it.
Definition content.cpp:185
bool hasHeader(const char *type) const
Definition content.cpp:618
qsizetype storageSize() const
Returns the size of this Content and all sub-Contents.
Definition content.cpp:639
void clear()
Clears the content, deleting all headers and sub-Contents.
Definition content.cpp:217
void appendContent(Content *content)
Appends a new sub-Content.
Definition content.cpp:488
QList< Content * > contents() const
For multipart contents, this will return a list of all multipart child contents.
Definition content.cpp:469
void setParent(Content *parent)
Sets a new parent to the Content and add to its contents list.
Definition content.cpp:739
void setPreamble(const QByteArray &preamble)
Sets the MIME preamble.
Definition content.cpp:101
void setBody(const QByteArray &body)
Sets the Content decoded body raw data.
Definition content.cpp:84
void changeEncoding(Headers::contentEncoding e)
Changes the encoding of this Content to e.
Definition content.cpp:532
Content * topLevel()
Returns the toplevel content object, 0 if there is no such object.
Definition content.cpp:767
QByteArray preamble() const
Returns the MIME preamble.
Definition content.cpp:96
void fromUnicodeString(const QString &s)
Sets the Content body to the given string using charset of the content type.
Definition content.cpp:411
void setHead(const QByteArray &head)
Sets the Content header raw data.
Definition content.cpp:71
QByteArray encodedContent(bool useCrLf=false) const
Returns a QByteArray containing the encoded Content, including the Content header and all sub-Content...
Definition content.cpp:237
void appendHeader(Headers::Base *h)
Appends the specified header to the headers of this Content.
Definition content.cpp:597
Baseclass of all header-classes.
Definition headers.h:97
virtual const char * type() const
Returns the type of this header (e.g.
Definition headers.cpp:123
Represents a "Content-Transfer-Encoding" header.
Definition headers.h:872
void setEncoding(contentEncoding e)
Sets the encoding to e.
Definition headers.cpp:1925
contentEncoding encoding() const
Returns the encoding specified in this header.
Definition headers.cpp:1921
Represents a "Content-Type" header.
Definition headers.h:992
bool isText() const
Returns true if the associated MIME entity is a text.
Definition headers.cpp:1679
void setPartialParams(int total, int number)
Sets parameters of a partial MIME entity.
Definition headers.cpp:1759
bool isEmpty() const override
Checks if this header contains any data.
Definition headers.cpp:1597
bool isMultipart() const
Returns true if the associated MIME entity is a multipart container.
Definition headers.cpp:1695
void clear() override
Deletes.
Definition headers.cpp:1601
void setMimeType(const QByteArray &mimeType)
Sets the mimetype.
Definition headers.cpp:1650
void setCharset(const QByteArray &s)
Sets the charset.
Definition headers.cpp:1712
QByteArray boundary() const
Returns the boundary (for multipart containers).
Definition headers.cpp:1716
void setBoundary(const QByteArray &s)
Sets the multipart container boundary.
Definition headers.cpp:1720
Represents a (email) message.
Definition message.h:65
QSharedPointer< Message > Ptr
A shared pointer to a message object.
Definition message.h:70
This file is part of the API for handling MIME data and defines the Content class.
contentEncoding
Various possible values for the "Content-Transfer-Encoding" header.
Definition headers.h:52
KCODECS_EXPORT QByteArray uudecode(QByteArrayView in)
KCODECS_EXPORT QByteArray base64Encode(QByteArrayView in)
KCODECS_EXPORT QByteArray quotedPrintableDecode(QByteArrayView in)
KCODECS_EXPORT QByteArray base64Decode(QByteArrayView in)
KCODECS_EXPORT QByteArray quotedPrintableEncode(QByteArrayView in, bool useCRLF=true)
iterator begin()
const_iterator constEnd() const const
typedef const_iterator
bool endsWith(QByteArrayView bv) const const
bool isEmpty() const const
typedef iterator
void resize(qsizetype newSize, char c)
qsizetype size() const const
bool startsWith(QByteArrayView bv) const const
void truncate(qsizetype pos)
qsizetype count() const const
pointer data()
void push_back(parameter_type value)
void reserve(qsizetype size)
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype length() const const
void truncate(qsizetype position)
bool isValid() const const
const char * name() const const
EncodedData< QByteArrayView > decode(QByteArrayView ba)
DecodedData< QStringView > encode(QStringView in)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:51:33 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.