KMime

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

KDE's Doxygen guidelines are available online.