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 <[email protected]>
8  SPDX-FileCopyrightText: 2009 Constantin Berzan <[email protected]>
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 <[email protected]>
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.h"
29 #include "kmime_util_p.h"
30 
31 #include <KCharsets>
32 #include <KCodecs/KCodecs>
33 
34 
35 #include <QTextCodec>
36 
37 using namespace KMime;
38 
39 namespace 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 
57 bool Content::hasContent() const
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 
97 {
98  d_ptr->preamble = preamble;
99 }
100 
102 {
103  return d_ptr->epilogue;
104 }
105 
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 
172 bool Content::isFrozen() const
173 {
174  return d_ptr->frozen;
175 }
176 
177 void 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 : qAsConst(d->headers)) {
201  if (!h->isEmpty()) {
202  newHead += h->as7BitString() + '\n';
203  }
204  }
205 
206  return newHead;
207 }
208 
210 {
211  Q_D(Content);
212  qDeleteAll(d->headers);
213  d->headers.clear();
214  clearContents();
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 seperator, otherwise add one.
235  * If we have enough newlines as sperator, than 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 : qAsConst(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 : {
330  KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64");
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.end());
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 
364 QString Content::decodedText(bool trimText, bool removeTrailingNewlines)
365 {
366  if (!d_ptr->decodeText(this)) { //this is not a text content !!
367  return QString();
368  }
369 
370  bool ok = true;
371  QTextCodec *codec =
373  if (!ok || codec == nullptr) { // no suitable codec found => try local settings and hope the best ;-)
374  codec = QTextCodec::codecForLocale();
375  QByteArray chset = codec->name();
376  contentType()->setCharset(chset);
377  }
378 
379  QString s = codec->toUnicode(d_ptr->body.data(), d_ptr->body.length());
380 
381  if (trimText || removeTrailingNewlines) {
382  int i;
383  for (i = s.length() - 1; i >= 0; --i) {
384  if (trimText) {
385  if (!s[i].isSpace()) {
386  break;
387  }
388  } else {
389  if (s[i] != QLatin1Char('\n')) {
390  break;
391  }
392  }
393  }
394  s.truncate(i + 1);
395  } else {
396  if (s.right(1) == QLatin1Char('\n')) {
397  s.chop(1); // remove trailing new-line
398  }
399  }
400 
401  return s;
402 }
403 
405 {
406  bool ok = true;
407  QTextCodec *codec =
409 
410  if (!ok) { // no suitable codec found => try local settings and hope the best ;-)
411  codec = QTextCodec::codecForLocale();
412  QByteArray chset = codec->name();
413  contentType()->setCharset(chset);
414  }
415 
416  d_ptr->body = codec->fromUnicode(s);
417  contentTransferEncoding()->setDecoded(true); //text is always decoded
418 }
419 
421 {
422  Content *ret = nullptr;
423 
424  //return the first content with mimetype=text/*
425  if (contentType()->isText()) {
426  ret = this;
427  } else {
428  const auto contents = d_ptr->contents();
429  for (Content *c : contents) {
430  if ((ret = c->textContent()) != nullptr) {
431  break;
432  }
433  }
434  }
435  return ret;
436 }
437 
439 {
440  QVector<Content*> result;
441 
442  auto ct = contentType(false);
443  if (ct && ct->isMultipart() && !ct->isSubtype("related") && !ct->isSubtype("alternative")) {
444  const QVector<Content*> contentsList = contents();
445  result.reserve(contentsList.count());
446  for (Content *child : contentsList) {
447  if (isAttachment(child))
448  result.push_back(child);
449  else
450  result += child->attachments();
451  }
452  }
453 
454  return result;
455 }
456 
458 {
459  return d_ptr->contents();
460 }
461 
462 void Content::replaceContent(Content *oldContent, Content *newContent)
463 {
464  Q_D( Content );
465  if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( oldContent ) ) {
466  return;
467  }
468 
469  d->multipartContents.removeAll( oldContent );
470  delete oldContent;
471  d->multipartContents.append( newContent );
472  if( newContent->parent() != this ) {
473  // If the content was part of something else, this will remove it from there.
474  newContent->setParent( this );
475  }
476 }
477 
478 
479 void Content::addContent(Content *c, bool prepend)
480 {
481  Q_D(Content);
482 
483  // This method makes no sense for encapsulated messages
484  Q_ASSERT(!bodyIsMessage());
485 
486  // If this message is single-part; make it multipart first.
487  if (d->multipartContents.isEmpty() && !contentType()->isMultipart()) {
488  // The current body will be our first sub-Content.
489  auto *main = new Content(this);
490 
491  // Move the MIME headers to the newly created sub-Content.
492  // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers
493  // are not moved to the subcontent; they remain with the top-level content.
494  for (auto it = d->headers.begin(); it != d->headers.end();) {
495  if ((*it)->isMimeHeader()) {
496  // Add to new content.
497  main->setHeader(*it);
498  // Remove from this content.
499  it = d->headers.erase(it);
500  } else {
501  ++it;
502  }
503  }
504 
505  // Adjust the Content-Type of the newly created sub-Content.
506  main->contentType()->setCategory(Headers::CCmixedPart);
507 
508  // Move the body to the new subcontent.
509  main->setBody(d->body);
510  d->body.clear();
511 
512  // Add the subcontent.
513  d->multipartContents.append(main);
514 
515  // Convert this content to "multipart/mixed".
517  ct->setMimeType("multipart/mixed");
518  ct->setBoundary(multiPartBoundary());
519  ct->setCategory(Headers::CCcontainer);
520  auto cte = contentTransferEncoding();
521  cte->setEncoding(Headers::CE7Bit);
522  cte->setDecoded(true);
523  }
524 
525  // Add the new content.
526  if (prepend) {
527  d->multipartContents.prepend(c);
528  } else {
529  d->multipartContents.append(c);
530  }
531 
532  if (c->parent() != this) {
533  // If the content was part of something else, this will remove it from there.
534  c->setParent(this);
535  }
536 }
537 
538 void Content::removeContent(Content *c, bool del)
539 {
540  Q_D(Content);
541  if (d->multipartContents.isEmpty() || !d->multipartContents.contains(c)) {
542  return;
543  }
544 
545  // This method makes no sense for encapsulated messages.
546  // Should be covered by the above assert already, though.
547  Q_ASSERT(!bodyIsMessage());
548 
549  d->multipartContents.removeAll(c);
550  if (del) {
551  delete c;
552  } else {
553  c->d_ptr->parent = nullptr;
554  }
555 
556  // If only one content is left, turn this content into a single-part.
557  if (d->multipartContents.count() == 1) {
558  Content *main = d->multipartContents.first();
559 
560  // Move all headers from the old subcontent to ourselves.
561  // NOTE: This also sets the new Content-Type.
562  const auto headers = main->d_ptr->headers;
563  for (Headers::Base *h : headers) {
564  setHeader(h); // Will remove the old one if present.
565  }
566  main->d_ptr->headers.clear();
567 
568  // Move the body.
569  d->body = main->body();
570 
571  // Delete the old subcontent.
572  delete main;
573  d->multipartContents.clear();
574  }
575 }
576 
578 {
579  // This method makes no sense for encapsulated messages, they are always 7bit
580  // encoded.
581  Q_ASSERT(!bodyIsMessage());
582 
584  if (enc->encoding() == e) {
585  // Nothing to do.
586  return;
587  }
588 
589  if (d_ptr->decodeText(this)) {
590  // This is textual content. Textual content is stored decoded.
591  Q_ASSERT(enc->isDecoded());
592  enc->setEncoding(e);
593  } else {
594  // This is non-textual content. Re-encode it.
595  if (e == Headers::CEbase64) {
596  KCodecs::base64Encode(decodedContent(), d_ptr->body, true);
597  enc->setEncoding(e);
598  enc->setDecoded(false);
599  } else {
600  // It only makes sense to convert binary stuff to base64.
601  Q_ASSERT(false);
602  }
603  }
604 }
605 
607 {
608  return d_ptr->headers;
609 }
610 
611 Headers::Base *Content::headerByType(const char *type) const
612 {
613  Q_ASSERT(type && *type);
614 
615  for (Headers::Base *h : qAsConst(d_ptr->headers)) {
616  if (h->is(type)) {
617  return h; // Found.
618  }
619  }
620 
621  return nullptr; // Not found.
622 }
623 
625 {
626  Q_ASSERT(type && *type);
627 
629 
630  for (Headers::Base *h : qAsConst(d_ptr->headers)) {
631  if (h->is(type)) {
632  result << h;
633  }
634  }
635 
636  return result;
637 }
638 
640 {
641  Q_ASSERT(h);
642  removeHeader(h->type());
643  appendHeader(h);
644 }
645 
647 {
648  Q_D(Content);
649  d->headers.append(h);
650 }
651 
652 bool Content::removeHeader(const char *type)
653 {
654  Q_D(Content);
655  const auto endIt = d->headers.end();
656  for (auto it = d->headers.begin(); it != endIt; ++it)
657  if ((*it)->is(type)) {
658  delete(*it);
659  d->headers.erase(it);
660  return true;
661  }
662 
663  return false;
664 }
665 
666 bool Content::hasHeader(const char* type) const
667 {
668  return headerByType(type) != nullptr;
669 }
670 
672 {
673  int ret = d_ptr->body.length();
674 
675  if (contentTransferEncoding()->encoding() == Headers::CEbase64) {
676  KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64");
677  return codec->maxEncodedSizeFor(ret);
678  }
679 
680  // Not handling quoted-printable here since that requires actually
681  // converting the content, and that is O(size_of_content).
682  // For quoted-printable, this is only an approximate size.
683 
684  return ret;
685 }
686 
688 {
689  const Q_D(Content);
690  int s = d->head.size();
691 
692  if (d->contents().isEmpty()) {
693  s += d->body.size();
694  } else {
695 
696  // FIXME: This should take into account the boundary headers that are added in
697  // encodedContent!
698  const auto contents = d->contents();
699  for (Content *c : contents) {
700  s += c->storageSize();
701  }
702  }
703 
704  return s;
705 }
706 
708 {
709  const Q_D(Content);
710  int ret = 0;
711  if (!isTopLevel()) {
712  ret += d->head.count('\n');
713  }
714  ret += d->body.count('\n');
715 
716  const auto contents = d->contents();
717  for (Content *c : contents) {
718  ret += c->lineCount();
719  }
720 
721  return ret;
722 }
723 
724 bool ContentPrivate::decodeText(Content *q)
725 {
727 
728  if (!q->contentType()->isText()) {
729  return false; //non textual data cannot be decoded here => use decodedContent() instead
730  }
731  if (enc->isDecoded()) {
732  return true; //nothing to do
733  }
734 
735  switch (enc->encoding()) {
736  case Headers::CEbase64 :
738  break;
739  case Headers::CEquPr :
741  break;
742  case Headers::CEuuenc :
744  break;
745  case Headers::CEbinary :
746  // nothing to decode
747  default :
748  break;
749  }
750  if (!body.endsWith("\n")) {
751  body.append("\n");
752  }
753  enc->setDecoded(true);
754  return true;
755 }
756 
758 {
759  return KMime::cachedCharset(QByteArrayLiteral("ISO-8859-1"));
760 }
761 
763 {
764  if (!index.isValid()) {
765  return const_cast<KMime::Content *>(this);
766  }
767  ContentIndex idx = index;
768  unsigned int i = idx.pop() - 1; // one-based -> zero-based index
769  if (i < static_cast<unsigned int>(d_ptr->contents().size())) {
770  return d_ptr->contents().at(i)->content(idx);
771  } else {
772  return nullptr;
773  }
774 }
775 
777 {
778  int i = d_ptr->contents().indexOf(content);
779  if (i >= 0) {
780  ContentIndex ci;
781  ci.push(i + 1); // zero-based -> one-based index
782  return ci;
783  }
784  // not found, we need to search recursively
785  for (int i = 0; i < d_ptr->contents().size(); ++i) {
786  ContentIndex ci = d_ptr->contents().at(i)->indexForContent(content);
787  if (ci.isValid()) {
788  // found it
789  ci.push(i + 1); // zero-based -> one-based index
790  return ci;
791  }
792  }
793  return ContentIndex(); // not found
794 }
795 
797 {
798  return d_ptr->parent == nullptr;
799 }
800 
802 {
803  // Make sure the Content is only in the contents list of one parent object
804  Content *oldParent = d_ptr->parent;
805  if (oldParent) {
806  if (!oldParent->contents().isEmpty() && oldParent->contents().contains(this)) {
807  oldParent->removeContent(this);
808  }
809  }
810 
811  d_ptr->parent = parent;
812  if (parent) {
813  if (!parent->contents().isEmpty() && !parent->contents().contains(this)) {
814  parent->addContent(this);
815  }
816  }
817 }
818 
820 {
821  return d_ptr->parent;
822 }
823 
825 {
826  auto *top = const_cast<Content *>(this);
827  Content *c = parent();
828  while (c) {
829  top = c;
830  c = c->parent();
831  }
832 
833  return top;
834 }
835 
837 {
838  Content *top = topLevel();
839  if (top) {
840  return top->indexForContent(const_cast<Content *>(this));
841  }
842 
843  return indexForContent(const_cast<Content *>(this));
844 }
845 
847 {
848  if (bodyIsMessage() && d_ptr->bodyAsMessage) {
849  return d_ptr->bodyAsMessage;
850  } else {
851  return Message::Ptr();
852  }
853 }
854 
856 {
857  // Use const_case here to work around API issue that neither header() nor hasHeader() are
858  // const, even though they should be
859  return const_cast<Content *>(this)->header<Headers::ContentType>(false) &&
860  const_cast<Content *>(this)->header<Headers::ContentType>(true)
861  ->mimeType().toLower() == "message/rfc822";
862 }
863 
864 // @cond PRIVATE
865 #define kmime_mk_header_accessor( type, method ) \
866  Headers::type *Content::method( bool create ) { \
867  return header<Headers::type>( create ); \
868  }
869 
870 kmime_mk_header_accessor(ContentType, contentType)
871 kmime_mk_header_accessor(ContentTransferEncoding, contentTransferEncoding)
872 kmime_mk_header_accessor(ContentDisposition, contentDisposition)
873 kmime_mk_header_accessor(ContentDescription, contentDescription)
874 kmime_mk_header_accessor(ContentLocation, contentLocation)
875 kmime_mk_header_accessor(ContentID, contentID)
876 
877 #undef kmime_mk_header_accessor
878 // @endcond
879 
880 void ContentPrivate::clearBodyMessage()
881 {
883 }
884 
885 QVector<Content*> ContentPrivate::contents() const
886 {
887  Q_ASSERT(multipartContents.isEmpty() || !bodyAsMessage);
888  if (bodyAsMessage) {
889  return QVector<Content*>() << bodyAsMessage.data();
890  } else {
891  return multipartContents;
892  }
893 }
894 
895 bool ContentPrivate::parseUuencoded(Content *q)
896 {
897  Parser::UUEncoded uup(body, KMime::extractHeader(head, "Subject"));
898  if (!uup.parse()) {
899  return false; // Parsing failed.
900  }
901 
903  ct->clear();
904 
905  if (uup.isPartial()) {
906  // This seems to be only a part of the message, so we treat it as "message/partial".
907  ct->setMimeType("message/partial");
908  //ct->setId( uniqueString() ); not needed yet
909  ct->setPartialParams(uup.partialCount(), uup.partialNumber());
910  q->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
911  } else {
912  // This is a complete message, so treat it as "multipart/mixed".
913  const auto prevBody = body;
914  body.clear();
915  ct->setMimeType("multipart/mixed");
916  ct->setBoundary(multiPartBoundary());
917  ct->setCategory(Headers::CCcontainer);
918  auto cte = q->contentTransferEncoding();
919  cte->setEncoding(Headers::CE7Bit);
920  cte->setDecoded(true);
921 
922  // Add the plain text part first.
923  Q_ASSERT(multipartContents.isEmpty());
924  {
925  auto *c = new Content(q);
926  c->contentType()->setMimeType("text/plain");
927  c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
928  c->setBody(uup.textPart());
929  multipartContents.append(c);
930  }
931 
932  // Now add each of the binary parts as sub-Contents.
933  for (int i = 0; i < uup.binaryParts().count(); ++i) {
934  auto *c = new Content(q);
935  c->contentType()->setMimeType(uup.mimeTypes().at(i));
936  c->contentType()->setName(QLatin1String(uup.filenames().at(i)), QByteArray(/*charset*/));
937  c->contentTransferEncoding()->setEncoding(Headers::CEuuenc);
938  c->contentTransferEncoding()->setDecoded(false);
939  c->contentDisposition()->setDisposition(Headers::CDattachment);
940  c->contentDisposition()->setFilename(QLatin1String(uup.filenames().at(i)));
941  // uup.binaryParts().at(i) does no longer have the uuencode header, which makes KCodecs fail since 5c66308c4786ef7fbf77b0e306e73f7d4ac3431b
942  c->setBody(prevBody);
943  c->changeEncoding(Headers::CEbase64); // Convert to base64.
944  multipartContents.append(c);
945  }
946  }
947 
948  return true; // Parsing successful.
949 }
950 
951 bool ContentPrivate::parseYenc(Content *q)
952 {
954  if (!yenc.parse()) {
955  return false; // Parsing failed.
956  }
957 
959  ct->clear();
960 
961  if (yenc.isPartial()) {
962  // Assume there is exactly one decoded part. Treat this as "message/partial".
963  ct->setMimeType("message/partial");
964  //ct->setId( uniqueString() ); not needed yet
965  ct->setPartialParams(yenc.partialCount(), yenc.partialNumber());
966  q->contentTransferEncoding()->setEncoding(Headers::CEbinary);
967  q->changeEncoding(Headers::CEbase64); // Convert to base64.
968  } else {
969  // This is a complete message, so treat it as "multipart/mixed".
970  body.clear();
971  ct->setMimeType("multipart/mixed");
972  ct->setBoundary(multiPartBoundary());
973  ct->setCategory(Headers::CCcontainer);
974  auto cte = q->contentTransferEncoding();
975  cte->setEncoding(Headers::CE7Bit);
976  cte->setDecoded(true);
977 
978  // Add the plain text part first.
979  Q_ASSERT(multipartContents.isEmpty());
980  {
981  auto *c = new Content(q);
982  c->contentType()->setMimeType("text/plain");
983  c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
984  c->setBody(yenc.textPart());
985  multipartContents.append(c);
986  }
987 
988  // Now add each of the binary parts as sub-Contents.
989  for (int i = 0; i < yenc.binaryParts().count(); i++) {
990  auto *c = new Content(q);
991  c->contentType()->setMimeType(yenc.mimeTypes().at(i));
992  c->contentType()->setName(QLatin1String(yenc.filenames().at(i)), QByteArray(/*charset*/));
993  c->contentTransferEncoding()->setEncoding(Headers::CEbinary);
994  c->contentDisposition()->setDisposition(Headers::CDattachment);
995  c->contentDisposition()->setFilename(QLatin1String(yenc.filenames().at(i)));
996  c->setBody(yenc.binaryParts().at(i)); // Yenc bodies are binary.
997  c->changeEncoding(Headers::CEbase64); // Convert to base64.
998  multipartContents.append(c);
999  }
1000  }
1001 
1002  return true; // Parsing successful.
1003 }
1004 
1005 bool ContentPrivate::parseMultipart(Content *q)
1006 {
1007  const Headers::ContentType *ct = q->contentType();
1008  const QByteArray boundary = ct->boundary();
1009  if (boundary.isEmpty()) {
1010  return false; // Parsing failed; invalid multipart content.
1011  }
1012  Parser::MultiPart mpp(body, boundary);
1013  if (!mpp.parse()) {
1014  return false; // Parsing failed.
1015  }
1016 
1017  preamble = mpp.preamble();
1018  epilogue = mpp.epilouge();
1019 
1020  // Determine the category of the subparts (used in attachments()).
1021  Headers::contentCategory cat;
1022  if (ct->isSubtype("alternative")) {
1023  cat = Headers::CCalternativePart;
1024  } else {
1025  cat = Headers::CCmixedPart; // Default to "mixed".
1026  }
1027 
1028  // Create a sub-Content for every part.
1029  Q_ASSERT(multipartContents.isEmpty());
1030  body.clear();
1031  const auto parts = mpp.parts();
1032  for (const QByteArray &part : parts) {
1033  auto *c = new Content(q);
1034  c->setContent(part);
1035  c->setFrozen(frozen);
1036  c->parse();
1037  c->contentType()->setCategory(cat);
1038  multipartContents.append(c);
1039  }
1040 
1041  return true; // Parsing successful.
1042 }
1043 
1044 } // namespace KMime
void addContent(Content *content, bool prepend=false)
Adds a new sub-Content.
QTextCodec * codecForName(const QString &name) const
QByteArray fromUnicode(const QString &str) const const
contentEncoding encoding() const
Returns the encoding specified in this header.
QByteArray encodedBody()
Like encodedContent(), with the difference that only the body will be returned, i.e.
virtual QByteArray assembleHeaders()
Reimplement this method if you need to assemble additional headers in a derived class.
void clear()
void truncate(int position)
bool isMultipart() const
Returns true if the associated MIME entity is a mulitpart container.
Headers::ContentLocation * contentLocation(bool create=true)
Returns the Content-Location header.
This file is part of the API for handling MIME data and defines the Content class.
virtual QByteArray name() const const =0
bool isText() const
Returns true if the associated MIME entity is a text.
bool isDecoded() const
Returns whether the Content containing this header is already decoded.
virtual int maxDecodedSizeFor(int insize, NewlineType newline=NewlineLF) const =0
Helper-class: tries to extract the data from a possibly uuencoded message.
void setMimeType(const QByteArray &mimeType)
Sets the mimetype.
typedef const_iterator
void setEpilogue(const QByteArray &epilogue)
Sets the MIME preamble.
bool hasHeader(const char *type) const
void setHeader(Headers::Base *h)
Sets the specified header to this Content.
bool isFrozen() const
Returns whether this Content is frozen.
void setFrozen(bool frozen=true)
Freezes this Content if frozen is true; otherwise unfreezes it.
bool isEmpty() const const
bool isValid() const
Returns true if this index is non-empty (valid).
unsigned int pop()
Removes and returns the top-most index.
void setCharset(const QByteArray &s)
Sets the charset.
bool startsWith(const QByteArray &ba) const const
void push(unsigned int index)
Adds index to the content index.
bool removeHeader()
Searches for the first header of type T, and deletes it, removing it from this Content.
virtual Decoder * makeDecoder(NewlineType newline=NewlineLF) const =0
void setHead(const QByteArray &head)
Sets the Content header raw data.
QByteArray body() const
Returns the Content body raw data.
ContentIndex index() const
Returns the index of this Content based on the topLevel() object.
Headers::ContentDisposition * contentDisposition(bool create=true)
Returns the Content-Disposition header.
bool isSubtype(const char *subtype) const
Tests if the mime sub-type equals subtype.
void chop(int n)
QByteArray encodedContent(bool useCrLf=false)
Returns a QByteArray containing the encoded Content, including the Content header and all sub-Content...
T * data() const const
QString decodedText(bool trimText=false, bool removeTrailingNewlines=false)
Returns the decoded text.
KCODECS_EXPORT QByteArray uudecode(const QByteArray &in)
QTextCodec * codecForLocale()
void resize(int size)
bool hasContent() const
Returns true if this Content object is not empty.
QByteArray decodedContent()
Returns the decoded Content body.
int size()
Returns the size of the Content body after encoding.
QVector< Content * > contents() const
For multipart contents, this will return a list of all multipart child contents.
Content * content(const ContentIndex &index) const
Returns the Content specified by the given index.
void parse()
Parses the Content.
QVector< Content * > attachments()
Returns all attachments below this node, recursively.
void fromUnicodeString(const QString &s)
Sets the Content body to the given string using charset of the content type.
void clear() override
Deletes.
Baseclass of all header-classes.
KCODECS_EXPORT QByteArray base64Encode(const QByteArray &in)
bool isEmpty() const override
Checks if this header contains any data.
void setPreamble(const QByteArray &preamble)
Sets the MIME preamble.
void setBody(const QByteArray &body)
Sets the Content body raw data.
Helper-class: splits a multipart-message into single parts as described in RFC 2046.
Definition: kmime_parsers.h:25
virtual ~Content()
Destroys this Content object.
QByteArray::iterator begin()
KCODECS_EXPORT QByteArray base64Decode(const QByteArray &in)
typedef iterator
Headers::ContentID * contentID(bool create=true)
Returns the Content-ID header.
void truncate(int pos)
void setPartialParams(int total, int number)
Sets parameters of a partial MIME entity.
KCODECS_EXPORT QByteArray quotedPrintableEncode(const QByteArray &in, bool useCRLF=true)
bool bodyIsMessage() const
Headers::ContentTransferEncoding * contentTransferEncoding(bool create=true)
Returns the Content-Transfer-Encoding header.
void setBoundary(const QByteArray &s)
Sets the mulitpart container boundary.
bool isTopLevel() const
Returns true if this is the top-level node in the MIME tree.
void assemble()
Generates the MIME content.
QString right(int n) const const
Headers::ContentType * contentType(bool create=true)
Returns the Content-Type header.
QByteArray & append(char ch)
void changeEncoding(Headers::contentEncoding e)
Changes the encoding of this Content to e.
void reserve(int size)
void setContent(const QByteArray &s)
Sets the Content to the given raw data, containing the Content head and body separated by two linefee...
Headers::Base * headerByType(const char *type) const
Returns the first header of type type, if it exists.
static KCharsets * charsets()
int storageSize() const
Returns the size of this Content and all sub-Contents.
QByteArray preamble() const
Returns the MIME preamble.
QByteArray epilogue() const
Returns the MIME preamble.
Content * topLevel() const
Returns the toplevel content object, 0 if there is no such object.
const T & at(int i) const const
QByteArray head() const
Returns the Content header raw data.
static Codec * codecForName(const char *name)
Represents a (email) message.
Definition: kmime_message.h:66
void removeContent(Content *content, bool del=false)
Removes the given sub-Content.
QByteArray boundary() const
Returns the boundary (for mulitpart containers).
bool needToEncode() const
Returns whether the Content containing this header needs to be encoded (i.e., if decoded() is true an...
void setDecoded(bool isDecoded=true)
Set whether the Content containing this header is already decoded.
void setParent(Content *parent)
Sets a new parent to the Content and add to its contents list.
static QByteArray defaultCharset()
Returns the charset that is used to decode RFC2047 strings in all headers and to decode the body if t...
QSharedPointer< Message > Ptr
A shared pointer to a message object.
Definition: kmime_message.h:72
QVector< Headers::Base * > headers() const
Returns all headers.
QSharedPointer< Message > bodyAsMessage() const
If this content is an encapsulated message, in which case bodyIsMessage() will return true...
void clear()
Clears the content, deleting all headers and sub-Contents.
Content * textContent()
Returns the first Content with mimetype text/.
virtual int maxEncodedSizeFor(int insize, NewlineType newline=NewlineLF) const =0
int count(const T &value) const const
ContentIndex indexForContent(Content *content) const
Returns the ContentIndex for the given Content, or an invalid index if the Content is not found withi...
Helper-class: tries to extract the data from a possibly yenc encoded message.
int length() const const
A class that encapsulates MIME encoded Content.
Definition: kmime_content.h:98
void setEncoding(contentEncoding e)
Sets the encoding to e.
void appendHeader(Headers::Base *h)
Appends the specified header to the headers of this Content.
void push_back(const T &value)
Content * parent() const
Returns the parent content object, or 0 if the content doesn&#39;t have a parent.
Content(Content *parent=nullptr)
Creates an empty Content object with a specified parent.
Represents a "Content-Transfer-Encoding" header.
Headers::ContentDescription * contentDescription(bool create=true)
Returns the Content-Description header.
int size() const const
void clearContents(bool del=true)
Removes all sub-Contents from this content.
Represents a "Content-Type" header.
virtual const char * type() const
Returns the type of this header (e.g.
bool endsWith(const QByteArray &ba) const const
QString toUnicode(const QByteArray &a) const const
QByteArray::iterator end()
KCODECS_EXPORT QByteArray quotedPrintableDecode(const QByteArray &in)
int lineCount() const
Line count of this Content and all sub-Contents.
QVector< Headers::Base * > headersByType(const char *type) const
Returns all type headers in the Content.
contentEncoding
Various possible values for the "Content-Transfer-Encoding" header.
Definition: kmime_headers.h:59
A class to uniquely identify message parts (Content) in a hierarchy.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Jun 17 2021 23:12:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.