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

KDE's Doxygen guidelines are available online.