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 : std::as_const(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 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 : {
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.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 
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 
455  return result;
456 }
457 
459 {
460  return d_ptr->contents();
461 }
462 
463 void Content::replaceContent(Content *oldContent, Content *newContent)
464 {
465  Q_D( Content );
466  if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( oldContent ) ) {
467  return;
468  }
469 
470  d->multipartContents.removeAll( oldContent );
471  delete oldContent;
472  d->multipartContents.append( newContent );
473  if( newContent->parent() != this ) {
474  // If the content was part of something else, this will remove it from there.
475  newContent->setParent( this );
476  }
477 }
478 
479 
480 void Content::addContent(Content *c, bool prepend)
481 {
482  Q_D(Content);
483 
484  // This method makes no sense for encapsulated messages
485  Q_ASSERT(!bodyIsMessage());
486 
487  // If this message is single-part; make it multipart first.
488  if (d->multipartContents.isEmpty() && !contentType()->isMultipart()) {
489  // The current body will be our first sub-Content.
490  auto *main = new Content(this);
491 
492  // Move the MIME headers to the newly created sub-Content.
493  // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers
494  // are not moved to the subcontent; they remain with the top-level content.
495  for (auto it = d->headers.begin(); it != d->headers.end();) {
496  if ((*it)->isMimeHeader()) {
497  // Add to new content.
498  main->setHeader(*it);
499  // Remove from this content.
500  it = d->headers.erase(it);
501  } else {
502  ++it;
503  }
504  }
505 
506  // Adjust the Content-Type of the newly created sub-Content.
507  main->contentType()->setCategory(Headers::CCmixedPart);
508 
509  // Move the body to the new subcontent.
510  main->setBody(d->body);
511  d->body.clear();
512 
513  // Add the subcontent.
514  d->multipartContents.append(main);
515 
516  // Convert this content to "multipart/mixed".
518  ct->setMimeType("multipart/mixed");
519  ct->setBoundary(multiPartBoundary());
520  ct->setCategory(Headers::CCcontainer);
521  auto cte = contentTransferEncoding();
522  cte->setEncoding(Headers::CE7Bit);
523  cte->setDecoded(true);
524  }
525 
526  // Add the new content.
527  if (prepend) {
528  d->multipartContents.prepend(c);
529  } else {
530  d->multipartContents.append(c);
531  }
532 
533  if (c->parent() != this) {
534  // If the content was part of something else, this will remove it from there.
535  c->setParent(this);
536  }
537 }
538 
539 void Content::removeContent(Content *c, bool del)
540 {
541  Q_D(Content);
542  if (d->multipartContents.isEmpty() || !d->multipartContents.contains(c)) {
543  return;
544  }
545 
546  // This method makes no sense for encapsulated messages.
547  // Should be covered by the above assert already, though.
548  Q_ASSERT(!bodyIsMessage());
549 
550  d->multipartContents.removeAll(c);
551  if (del) {
552  delete c;
553  } else {
554  c->d_ptr->parent = nullptr;
555  }
556 
557  // If only one content is left, turn this content into a single-part.
558  if (d->multipartContents.count() == 1) {
559  Content *main = d->multipartContents.constFirst();
560 
561  // Move all headers from the old subcontent to ourselves.
562  // NOTE: This also sets the new Content-Type.
563  const auto headers = main->d_ptr->headers;
564  for (Headers::Base *h : headers) {
565  setHeader(h); // Will remove the old one if present.
566  }
567  main->d_ptr->headers.clear();
568 
569  // Move the body.
570  d->body = main->body();
571 
572  // Delete the old subcontent.
573  delete main;
574  d->multipartContents.clear();
575  }
576 }
577 
579 {
580  // This method makes no sense for encapsulated messages, they are always 7bit
581  // encoded.
582  Q_ASSERT(!bodyIsMessage());
583 
585  if (enc->encoding() == e) {
586  // Nothing to do.
587  return;
588  }
589 
590  if (d_ptr->decodeText(this)) {
591  // This is textual content. Textual content is stored decoded.
592  Q_ASSERT(enc->isDecoded());
593  enc->setEncoding(e);
594  } else {
595  // This is non-textual content. Re-encode it.
596  if (e == Headers::CEbase64) {
597  KCodecs::base64Encode(decodedContent(), d_ptr->body, true);
598  enc->setEncoding(e);
599  enc->setDecoded(false);
600  } else {
601  // It only makes sense to convert binary stuff to base64.
602  Q_ASSERT(false);
603  }
604  }
605 }
606 
608 {
609  return d_ptr->headers;
610 }
611 
612 Headers::Base *Content::headerByType(const char *type) const
613 {
614  Q_ASSERT(type && *type);
615 
616  for (Headers::Base *h : std::as_const(d_ptr->headers)) {
617  if (h->is(type)) {
618  return h; // Found.
619  }
620  }
621 
622  return nullptr; // Not found.
623 }
624 
626 {
627  Q_ASSERT(type && *type);
628 
630 
631  for (Headers::Base *h : std::as_const(d_ptr->headers)) {
632  if (h->is(type)) {
633  result << h;
634  }
635  }
636 
637  return result;
638 }
639 
641 {
642  Q_ASSERT(h);
643  removeHeader(h->type());
644  appendHeader(h);
645 }
646 
648 {
649  Q_D(Content);
650  d->headers.append(h);
651 }
652 
653 bool Content::removeHeader(const char *type)
654 {
655  Q_D(Content);
656  const auto endIt = d->headers.end();
657  for (auto it = d->headers.begin(); it != endIt; ++it) {
658  if ((*it)->is(type)) {
659  delete(*it);
660  d->headers.erase(it);
661  return true;
662  }
663  }
664 
665  return false;
666 }
667 
668 bool Content::hasHeader(const char* type) const
669 {
670  return headerByType(type) != nullptr;
671 }
672 
674 {
675  int ret = d_ptr->body.length();
676 
677  if (contentTransferEncoding()->encoding() == Headers::CEbase64) {
678  KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64");
679  return codec->maxEncodedSizeFor(ret);
680  }
681 
682  // Not handling quoted-printable here since that requires actually
683  // converting the content, and that is O(size_of_content).
684  // For quoted-printable, this is only an approximate size.
685 
686  return ret;
687 }
688 
690 {
691  const Q_D(Content);
692  int s = d->head.size();
693 
694  if (d->contents().isEmpty()) {
695  s += d->body.size();
696  } else {
697 
698  // FIXME: This should take into account the boundary headers that are added in
699  // encodedContent!
700  const auto contents = d->contents();
701  for (Content *c : contents) {
702  s += c->storageSize();
703  }
704  }
705 
706  return s;
707 }
708 
710 {
711  const Q_D(Content);
712  int ret = 0;
713  if (!isTopLevel()) {
714  ret += d->head.count('\n');
715  }
716  ret += d->body.count('\n');
717 
718  const auto contents = d->contents();
719  for (Content *c : contents) {
720  ret += c->lineCount();
721  }
722 
723  return ret;
724 }
725 
726 bool ContentPrivate::decodeText(Content *q)
727 {
729 
730  if (!q->contentType()->isText()) {
731  return false; //non textual data cannot be decoded here => use decodedContent() instead
732  }
733  if (enc->isDecoded()) {
734  return true; //nothing to do
735  }
736 
737  switch (enc->encoding()) {
738  case Headers::CEbase64 :
740  break;
741  case Headers::CEquPr :
743  break;
744  case Headers::CEuuenc :
746  break;
747  case Headers::CEbinary :
748  // nothing to decode
749  default :
750  break;
751  }
752  if (!body.endsWith("\n")) {
753  body.append("\n");
754  }
755  enc->setDecoded(true);
756  return true;
757 }
758 
760 {
761  return KMime::cachedCharset(QByteArrayLiteral("ISO-8859-1"));
762 }
763 
765 {
766  if (!index.isValid()) {
767  return const_cast<KMime::Content *>(this);
768  }
769  ContentIndex idx = index;
770  unsigned int i = idx.pop() - 1; // one-based -> zero-based index
771  if (i < static_cast<unsigned int>(d_ptr->contents().size())) {
772  return d_ptr->contents().at(i)->content(idx);
773  } else {
774  return nullptr;
775  }
776 }
777 
779 {
780  int i = d_ptr->contents().indexOf(content);
781  if (i >= 0) {
782  ContentIndex ci;
783  ci.push(i + 1); // zero-based -> one-based index
784  return ci;
785  }
786  // not found, we need to search recursively
787  for (int i = 0; i < d_ptr->contents().size(); ++i) {
788  ContentIndex ci = d_ptr->contents().at(i)->indexForContent(content);
789  if (ci.isValid()) {
790  // found it
791  ci.push(i + 1); // zero-based -> one-based index
792  return ci;
793  }
794  }
795  return ContentIndex(); // not found
796 }
797 
799 {
800  return d_ptr->parent == nullptr;
801 }
802 
804 {
805  // Make sure the Content is only in the contents list of one parent object
806  Content *oldParent = d_ptr->parent;
807  if (oldParent) {
808  if (!oldParent->contents().isEmpty() && oldParent->contents().contains(this)) {
809  oldParent->removeContent(this);
810  }
811  }
812 
813  d_ptr->parent = parent;
814  if (parent) {
815  if (!parent->contents().isEmpty() && !parent->contents().contains(this)) {
816  parent->addContent(this);
817  }
818  }
819 }
820 
822 {
823  return d_ptr->parent;
824 }
825 
827 {
828  auto *top = const_cast<Content *>(this);
829  Content *c = parent();
830  while (c) {
831  top = c;
832  c = c->parent();
833  }
834 
835  return top;
836 }
837 
839 {
840  Content *top = topLevel();
841  if (top) {
842  return top->indexForContent(const_cast<Content *>(this));
843  }
844 
845  return indexForContent(const_cast<Content *>(this));
846 }
847 
849 {
850  if (bodyIsMessage() && d_ptr->bodyAsMessage) {
851  return d_ptr->bodyAsMessage;
852  } else {
853  return Message::Ptr();
854  }
855 }
856 
858 {
859  // Use const_case here to work around API issue that neither header() nor hasHeader() are
860  // const, even though they should be
861  return const_cast<Content *>(this)->header<Headers::ContentType>(false) &&
862  const_cast<Content *>(this)->header<Headers::ContentType>(true)
863  ->mimeType().toLower() == "message/rfc822";
864 }
865 
866 // @cond PRIVATE
867 #define kmime_mk_header_accessor( type, method ) \
868  Headers::type *Content::method( bool create ) { \
869  return header<Headers::type>( create ); \
870  }
871 
872 kmime_mk_header_accessor(ContentType, contentType)
873 kmime_mk_header_accessor(ContentTransferEncoding, contentTransferEncoding)
874 kmime_mk_header_accessor(ContentDisposition, contentDisposition)
875 kmime_mk_header_accessor(ContentDescription, contentDescription)
876 kmime_mk_header_accessor(ContentLocation, contentLocation)
877 kmime_mk_header_accessor(ContentID, contentID)
878 
879 #undef kmime_mk_header_accessor
880 // @endcond
881 
882 void ContentPrivate::clearBodyMessage()
883 {
885 }
886 
887 QVector<Content*> ContentPrivate::contents() const
888 {
889  Q_ASSERT(multipartContents.isEmpty() || !bodyAsMessage);
890  if (bodyAsMessage) {
891  return QVector<Content*>() << bodyAsMessage.data();
892  } else {
893  return multipartContents;
894  }
895 }
896 
897 bool ContentPrivate::parseUuencoded(Content *q)
898 {
899  Parser::UUEncoded uup(body, KMime::extractHeader(head, "Subject"));
900  if (!uup.parse()) {
901  return false; // Parsing failed.
902  }
903 
905  ct->clear();
906 
907  if (uup.isPartial()) {
908  // This seems to be only a part of the message, so we treat it as "message/partial".
909  ct->setMimeType("message/partial");
910  //ct->setId( uniqueString() ); not needed yet
911  ct->setPartialParams(uup.partialCount(), uup.partialNumber());
912  q->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
913  } else {
914  // This is a complete message, so treat it as "multipart/mixed".
915  const auto prevBody = body;
916  body.clear();
917  ct->setMimeType("multipart/mixed");
918  ct->setBoundary(multiPartBoundary());
919  ct->setCategory(Headers::CCcontainer);
920  auto cte = q->contentTransferEncoding();
921  cte->setEncoding(Headers::CE7Bit);
922  cte->setDecoded(true);
923 
924  // Add the plain text part first.
925  Q_ASSERT(multipartContents.isEmpty());
926  {
927  auto *c = new Content(q);
928  c->contentType()->setMimeType("text/plain");
929  c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
930  c->setBody(uup.textPart());
931  multipartContents.append(c);
932  }
933 
934  // Now add each of the binary parts as sub-Contents.
935  for (int i = 0; i < uup.binaryParts().count(); ++i) {
936  auto *c = new Content(q);
937  c->contentType()->setMimeType(uup.mimeTypes().at(i));
938  c->contentType()->setName(QLatin1String(uup.filenames().at(i)), QByteArray(/*charset*/));
939  c->contentTransferEncoding()->setEncoding(Headers::CEuuenc);
940  c->contentTransferEncoding()->setDecoded(false);
941  c->contentDisposition()->setDisposition(Headers::CDattachment);
942  c->contentDisposition()->setFilename(QLatin1String(uup.filenames().at(i)));
943  // uup.binaryParts().at(i) does no longer have the uuencode header, which makes KCodecs fail since 5c66308c4786ef7fbf77b0e306e73f7d4ac3431b
944  c->setBody(prevBody);
945  c->changeEncoding(Headers::CEbase64); // Convert to base64.
946  multipartContents.append(c);
947  }
948  }
949 
950  return true; // Parsing successful.
951 }
952 
953 bool ContentPrivate::parseYenc(Content *q)
954 {
956  if (!yenc.parse()) {
957  return false; // Parsing failed.
958  }
959 
961  ct->clear();
962 
963  if (yenc.isPartial()) {
964  // Assume there is exactly one decoded part. Treat this as "message/partial".
965  ct->setMimeType("message/partial");
966  //ct->setId( uniqueString() ); not needed yet
967  ct->setPartialParams(yenc.partialCount(), yenc.partialNumber());
968  q->contentTransferEncoding()->setEncoding(Headers::CEbinary);
969  q->changeEncoding(Headers::CEbase64); // Convert to base64.
970  } else {
971  // This is a complete message, so treat it as "multipart/mixed".
972  body.clear();
973  ct->setMimeType("multipart/mixed");
974  ct->setBoundary(multiPartBoundary());
975  ct->setCategory(Headers::CCcontainer);
976  auto cte = q->contentTransferEncoding();
977  cte->setEncoding(Headers::CE7Bit);
978  cte->setDecoded(true);
979 
980  // Add the plain text part first.
981  Q_ASSERT(multipartContents.isEmpty());
982  {
983  auto *c = new Content(q);
984  c->contentType()->setMimeType("text/plain");
985  c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
986  c->setBody(yenc.textPart());
987  multipartContents.append(c);
988  }
989 
990  // Now add each of the binary parts as sub-Contents.
991  for (int i = 0; i < yenc.binaryParts().count(); i++) {
992  auto *c = new Content(q);
993  c->contentType()->setMimeType(yenc.mimeTypes().at(i));
994  c->contentType()->setName(QLatin1String(yenc.filenames().at(i)), QByteArray(/*charset*/));
995  c->contentTransferEncoding()->setEncoding(Headers::CEbinary);
996  c->contentDisposition()->setDisposition(Headers::CDattachment);
997  c->contentDisposition()->setFilename(QLatin1String(yenc.filenames().at(i)));
998  c->setBody(yenc.binaryParts().at(i)); // Yenc bodies are binary.
999  c->changeEncoding(Headers::CEbase64); // Convert to base64.
1000  multipartContents.append(c);
1001  }
1002  }
1003 
1004  return true; // Parsing successful.
1005 }
1006 
1007 bool ContentPrivate::parseMultipart(Content *q)
1008 {
1009  const Headers::ContentType *ct = q->contentType();
1010  const QByteArray boundary = ct->boundary();
1011  if (boundary.isEmpty()) {
1012  return false; // Parsing failed; invalid multipart content.
1013  }
1014  Parser::MultiPart mpp(body, boundary);
1015  if (!mpp.parse()) {
1016  return false; // Parsing failed.
1017  }
1018 
1019  preamble = mpp.preamble();
1020  epilogue = mpp.epilouge();
1021 
1022  // Determine the category of the subparts (used in attachments()).
1023  Headers::contentCategory cat;
1024  if (ct->isSubtype("alternative")) {
1025  cat = Headers::CCalternativePart;
1026  } else {
1027  cat = Headers::CCmixedPart; // Default to "mixed".
1028  }
1029 
1030  // Create a sub-Content for every part.
1031  Q_ASSERT(multipartContents.isEmpty());
1032  body.clear();
1033  const auto parts = mpp.parts();
1034  for (const QByteArray &part : parts) {
1035  auto *c = new Content(q);
1036  c->setContent(part);
1037  c->setFrozen(frozen);
1038  c->parse();
1039  c->contentType()->setCategory(cat);
1040  multipartContents.append(c);
1041  }
1042 
1043  return true; // Parsing successful.
1044 }
1045 
1046 } // 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 multipart 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 multipart 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 multipart 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)
QByteArray::const_iterator constEnd() const const
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
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 Mon Sep 20 2021 23:14:43 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.