File indexing completed on 2024-05-05 09:36:10

0001 /*
0002     kmime_content.cpp
0003 
0004     KMime, the KDE Internet mail/usenet news message library.
0005     SPDX-FileCopyrightText: 2001 the KMime authors.
0006     See file AUTHORS for details
0007     SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
0008     SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 /**
0013   @file
0014   This file is part of the API for handling @ref MIME data and
0015   defines the Content class.
0016 
0017   @brief
0018   Defines the Content class.
0019 
0020   @authors the KMime authors (see AUTHORS file),
0021   Volker Krause \<vkrause@kde.org\>
0022 */
0023 #include "kmime_content.h"
0024 #include "kmime_content_p.h"
0025 #include "kmime_message.h"
0026 #include "kmime_header_parsing.h"
0027 #include "kmime_header_parsing_p.h"
0028 #include "kmime_parsers_p.h"
0029 #include "kmime_util_p.h"
0030 
0031 #include <KCodecs>
0032 
0033 #include <QDebug>
0034 #include <QStringDecoder>
0035 #include <QStringEncoder>
0036 
0037 using namespace KMime;
0038 
0039 namespace KMime
0040 {
0041 
0042 Content::Content(Content *parent)
0043     : d_ptr(new ContentPrivate)
0044 {
0045     d_ptr->parent = parent;
0046 }
0047 
0048 Content::~Content()
0049 {
0050     Q_D(Content);
0051     qDeleteAll(d->headers);
0052     d->headers.clear();
0053     delete d_ptr;
0054     d_ptr = nullptr;
0055 }
0056 
0057 bool Content::hasContent() const
0058 {
0059     return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents().isEmpty();
0060 }
0061 
0062 void Content::setContent(const QByteArray &s)
0063 {
0064     Q_D(Content);
0065     KMime::HeaderParsing::extractHeaderAndBody(s, d->head, d->body);
0066 }
0067 
0068 QByteArray Content::head() const
0069 {
0070     return d_ptr->head;
0071 }
0072 
0073 void Content::setHead(const QByteArray &head)
0074 {
0075     d_ptr->head = head;
0076     if (!head.endsWith('\n')) {
0077         d_ptr->head += '\n';
0078     }
0079 }
0080 
0081 QByteArray Content::body() const
0082 {
0083     return d_ptr->body;
0084 }
0085 
0086 void Content::setBody(const QByteArray &body)
0087 {
0088     d_ptr->body = body;
0089 }
0090 
0091 QByteArray Content::preamble() const
0092 {
0093     return d_ptr->preamble;
0094 }
0095 
0096 void Content::setPreamble(const QByteArray &preamble)
0097 {
0098     d_ptr->preamble = preamble;
0099 }
0100 
0101 QByteArray Content::epilogue() const
0102 {
0103     return d_ptr->epilogue;
0104 }
0105 
0106 void Content::setEpilogue(const QByteArray &epilogue)
0107 {
0108     d_ptr->epilogue = epilogue;
0109 }
0110 
0111 void Content::parse()
0112 {
0113     Q_D(Content);
0114 
0115     // Clean up old headers and parse them again.
0116     qDeleteAll(d->headers);
0117     d->headers.clear();
0118     d->headers = HeaderParsing::parseHeaders(d->head);
0119 
0120     // If we are frozen, save the body as-is. This is done because parsing
0121     // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.)
0122     if (d->frozen) {
0123         d->frozenBody = d->body;
0124     }
0125 
0126     // Clean up old sub-Contents and parse them again.
0127     qDeleteAll(d->multipartContents);
0128     d->multipartContents.clear();
0129     d->clearBodyMessage();
0130     Headers::ContentType *ct = contentType();
0131     if (ct->isEmpty()) { //Set default content-type as defined in https://tools.ietf.org/html/rfc2045#page-10 (5.2.  Content-Type Defaults)
0132         ct->setMimeType("text/plain");
0133         ct->setCharset("us-ascii");
0134     }
0135     if (ct->isText()) {
0136         // This content is either text, or of unknown type.
0137 
0138         if (d->parseUuencoded(this)) {
0139             // This is actually uuencoded content generated by broken software.
0140         } else if (d->parseYenc(this)) {
0141             // This is actually yenc content generated by broken software.
0142         } else {
0143             // This is just plain text.
0144         }
0145     } else if (ct->isMultipart()) {
0146         // This content claims to be MIME multipart.
0147 
0148         if (d->parseMultipart(this)) {
0149             // This is actual MIME multipart content.
0150         } else {
0151             // Parsing failed; treat this content as "text/plain".
0152             ct->setMimeType("text/plain");
0153             ct->setCharset("US-ASCII");
0154         }
0155     } else {
0156         // This content is something else, like an encapsulated message or a binary attachment
0157         // or something like that
0158         if (bodyIsMessage()) {
0159             d->bodyAsMessage = Message::Ptr(new Message);
0160             d->bodyAsMessage->setContent(d->body);
0161             d->bodyAsMessage->setFrozen(d->frozen);
0162             d->bodyAsMessage->parse();
0163             d->bodyAsMessage->d_ptr->parent = this;
0164 
0165             // Clear the body, as it is now represented by d->bodyAsMessage. This is the same behavior
0166             // as with multipart contents, since parseMultipart() clears the body as well
0167             d->body.clear();
0168         }
0169     }
0170 }
0171 
0172 bool Content::isFrozen() const
0173 {
0174     return d_ptr->frozen;
0175 }
0176 
0177 void Content::setFrozen(bool frozen)
0178 {
0179     d_ptr->frozen = frozen;
0180 }
0181 
0182 void Content::assemble()
0183 {
0184     Q_D(Content);
0185     if (d->frozen) {
0186         return;
0187     }
0188 
0189     d->head = assembleHeaders();
0190     const auto contentsList = contents();
0191     for (Content *c : contentsList) {
0192         c->assemble();
0193     }
0194 }
0195 
0196 QByteArray Content::assembleHeaders()
0197 {
0198     Q_D(Content);
0199     QByteArray newHead;
0200     for (const Headers::Base *h : std::as_const(d->headers)) {
0201         if (!h->isEmpty()) {
0202             newHead += foldHeader(h->as7BitString()) + '\n';
0203         }
0204     }
0205 
0206     return newHead;
0207 }
0208 
0209 void Content::clear()
0210 {
0211     Q_D(Content);
0212     qDeleteAll(d->headers);
0213     d->headers.clear();
0214     clearContents();
0215     d->head.clear();
0216     d->body.clear();
0217 }
0218 
0219 void Content::clearContents(bool del)
0220 {
0221     Q_D(Content);
0222     if (del) {
0223         qDeleteAll(d->multipartContents);
0224     }
0225     d->multipartContents.clear();
0226     d->clearBodyMessage();
0227 }
0228 
0229 QByteArray Content::encodedContent(bool useCrLf)
0230 {
0231     QByteArray encodedContentData = head();           // return value; initialise with the head data
0232     const QByteArray encodedBodyData = encodedBody();
0233 
0234     /* Make sure that head and body have at least two newlines as separator, otherwise add one.
0235      * If we have enough newlines as sperator, then we should not change the number of newlines
0236      * to not break digital signatures
0237      */
0238     if (!encodedContentData.endsWith("\n\n") &&
0239         !encodedBodyData.startsWith("\n\n") &&
0240         !(encodedContentData.endsWith("\n") && encodedBodyData.startsWith("\n"))){
0241         encodedContentData += '\n';
0242     }
0243     encodedContentData += encodedBodyData;
0244 
0245     if (useCrLf) {
0246         return LFtoCRLF(encodedContentData);
0247     } else {
0248         return encodedContentData;
0249     }
0250 }
0251 
0252 QByteArray Content::encodedBody()
0253 {
0254     Q_D(Content);
0255     QByteArray e;
0256     // Body.
0257     if (d->frozen) {
0258         // This Content is frozen.
0259         if (d->frozenBody.isEmpty()) {
0260             // This Content has never been parsed.
0261             e += d->body;
0262         } else {
0263             // Use the body as it was before parsing.
0264             e += d->frozenBody;
0265         }
0266     } else if (bodyIsMessage() && d->bodyAsMessage) {
0267         // This is an encapsulated message
0268         // No encoding needed, as the ContentTransferEncoding can only be 7bit
0269         // for encapsulated messages
0270         e += d->bodyAsMessage->encodedContent();
0271     } else if (!d->body.isEmpty()) {
0272         // This is a single-part Content.
0273         Headers::ContentTransferEncoding *enc = contentTransferEncoding();
0274 
0275         if (enc->needToEncode()) {
0276             if (enc->encoding() == Headers::CEquPr) {
0277                 e += KCodecs::quotedPrintableEncode(d->body, false);
0278             } else {
0279                 QByteArray encoded;
0280                 KCodecs::base64Encode(d->body, encoded, true);
0281                 e += encoded;
0282                 e += '\n';
0283             }
0284         } else {
0285             e += d->body;
0286         }
0287     }
0288 
0289     if (!d->frozen && !d->multipartContents.isEmpty()) {
0290         // This is a multipart Content.
0291         Headers::ContentType *ct = contentType();
0292         QByteArray boundary = "\n--" + ct->boundary();
0293 
0294         if (!d->preamble.isEmpty()) {
0295             e += d->preamble;
0296         }
0297 
0298         //add all (encoded) contents separated by boundaries
0299         for (Content *c : std::as_const(d->multipartContents)) {
0300             e += boundary + '\n';
0301             e += c->encodedContent(false);    // don't convert LFs here, we do that later!!!!!
0302         }
0303         //finally append the closing boundary
0304         e += boundary + "--\n";
0305 
0306         if (!d->epilogue.isEmpty()) {
0307             e += d->epilogue;
0308         }
0309     }
0310     return e;
0311 }
0312 
0313 QByteArray Content::decodedContent()
0314 {
0315     QByteArray ret;
0316     Headers::ContentTransferEncoding *ec = contentTransferEncoding();
0317     bool removeTrailingNewline = false;
0318 
0319     if (d_ptr->body.isEmpty()) {
0320         return ret;
0321     }
0322 
0323     if (ec->isDecoded()) {
0324         ret = d_ptr->body;
0325         //Laurent Fix bug #311267
0326         //removeTrailingNewline = true;
0327     } else {
0328         switch (ec->encoding()) {
0329         case Headers::CEbase64 : {
0330             KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64");
0331             Q_ASSERT(codec);
0332             ret.resize(codec->maxDecodedSizeFor(d_ptr->body.size()));
0333             QScopedPointer<KCodecs::Decoder> decoder(codec->makeDecoder());
0334             QByteArray::const_iterator inputIt = d_ptr->body.constBegin();
0335             QByteArray::iterator resultIt = ret.begin();
0336             decoder->decode(inputIt, d_ptr->body.constEnd(), resultIt, ret.constEnd());
0337             ret.truncate(resultIt - ret.begin());
0338             break;
0339         }
0340         case Headers::CEquPr :
0341             ret = KCodecs::quotedPrintableDecode(d_ptr->body);
0342             removeTrailingNewline = true;
0343             break;
0344         case Headers::CEuuenc :
0345             KCodecs::uudecode(d_ptr->body, ret);
0346             break;
0347         case Headers::CEbinary :
0348             ret = d_ptr->body;
0349             removeTrailingNewline = false;
0350             break;
0351         default :
0352             ret = d_ptr->body;
0353             removeTrailingNewline = true;
0354         }
0355     }
0356 
0357     if (removeTrailingNewline && (ret.size() > 0) && (ret[ret.size() - 1] == '\n')) {
0358         ret.resize(ret.size() - 1);
0359     }
0360 
0361     return ret;
0362 }
0363 
0364 QString Content::decodedText(bool trimText, bool removeTrailingNewlines)
0365 {
0366     if (!d_ptr->decodeText(this)) {   //this is not a text content !!
0367       return {};
0368     }
0369 
0370     QStringDecoder codec(contentType()->charset().constData());
0371     if (!codec.isValid()) {   // no suitable codec found => try local settings and hope the best ;-)
0372         codec = QStringDecoder(QStringDecoder::System);
0373         QByteArray chset = codec.name();
0374         contentType()->setCharset(chset);
0375     }
0376 
0377     QString s = codec.decode(d_ptr->body);
0378 
0379     if (trimText || removeTrailingNewlines) {
0380         int i;
0381         for (i = s.length() - 1; i >= 0; --i) {
0382             if (trimText) {
0383                 if (!s[i].isSpace()) {
0384                     break;
0385                 }
0386             } else {
0387                 if (s[i] != QLatin1Char('\n')) {
0388                     break;
0389                 }
0390             }
0391         }
0392         s.truncate(i + 1);
0393     } else {
0394         if (s.right(1) == QLatin1Char('\n')) {
0395             s.chop(1);   // remove trailing new-line
0396         }
0397     }
0398 
0399     return s;
0400 }
0401 
0402 void Content::fromUnicodeString(const QString &s)
0403 {
0404     QStringEncoder codec(contentType()->charset().constData());
0405 
0406     if (!codec.isValid()) {   // no suitable codec found => try local settings and hope the best ;-)
0407         codec = QStringEncoder(QStringEncoder::System);
0408         QByteArray chset = codec.name();
0409         contentType()->setCharset(chset);
0410     }
0411 
0412     d_ptr->body = codec.encode(s);
0413     contentTransferEncoding()->setDecoded(true);   //text is always decoded
0414 }
0415 
0416 Content *Content::textContent()
0417 {
0418     Content *ret = nullptr;
0419 
0420     //return the first content with mimetype=text/*
0421     if (contentType()->isText()) {
0422         ret = this;
0423     } else {
0424         const auto contents = d_ptr->contents();
0425         for (Content *c : contents) {
0426             if ((ret = c->textContent()) != nullptr) {
0427                 break;
0428             }
0429         }
0430     }
0431     return ret;
0432 }
0433 
0434 QList<Content *> Content::attachments() {
0435     QList<Content *> result;
0436 
0437     auto ct = contentType(false);
0438     if (ct && ct->isMultipart() &&
0439         !ct->isSubtype("related") /* && !ct->isSubtype("alternative")*/) {
0440         const QList<Content *> contentsList = contents();
0441         result.reserve(contentsList.count());
0442         for (Content *child : contentsList) {
0443             if (isAttachment(child)) {
0444                 result.push_back(child);
0445             } else {
0446                 result += child->attachments();
0447             }
0448       }
0449     }
0450 
0451     return result;
0452 }
0453 
0454 QList<Content *> Content::contents() const { return d_ptr->contents(); }
0455 
0456 void Content::replaceContent(Content *oldContent, Content *newContent)
0457 {
0458     Q_D( Content );
0459     if ( d->multipartContents.isEmpty() || !d->multipartContents.contains( oldContent ) ) {
0460       return;
0461     }
0462 
0463     d->multipartContents.removeAll( oldContent );
0464     delete oldContent;
0465     d->multipartContents.append( newContent );
0466     if( newContent->parent() != this ) {
0467       // If the content was part of something else, this will remove it from there.
0468       newContent->setParent( this );
0469     }
0470 }
0471 
0472 
0473 void Content::addContent(Content *c, bool prepend)
0474 {
0475     Q_D(Content);
0476 
0477     // This method makes no sense for encapsulated messages
0478     Q_ASSERT(!bodyIsMessage());
0479 
0480     // If this message is single-part; make it multipart first.
0481     if (d->multipartContents.isEmpty() && !contentType()->isMultipart()) {
0482         // The current body will be our first sub-Content.
0483         auto main = new Content(this);
0484 
0485         // Move the MIME headers to the newly created sub-Content.
0486         // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers
0487         // are not moved to the subcontent; they remain with the top-level content.
0488         for (auto it = d->headers.begin(); it != d->headers.end();) {
0489             if ((*it)->isMimeHeader()) {
0490                 // Add to new content.
0491                 main->setHeader(*it);
0492                 // Remove from this content.
0493                 it = d->headers.erase(it);
0494             } else {
0495                 ++it;
0496             }
0497         }
0498 
0499         // Adjust the Content-Type of the newly created sub-Content.
0500         main->contentType()->setCategory(Headers::CCmixedPart);
0501 
0502         // Move the body to the new subcontent.
0503         main->setBody(d->body);
0504         d->body.clear();
0505 
0506         // Add the subcontent.
0507         d->multipartContents.append(main);
0508 
0509         // Convert this content to "multipart/mixed".
0510         Headers::ContentType *ct = contentType();
0511         ct->setMimeType("multipart/mixed");
0512         ct->setBoundary(multiPartBoundary());
0513         ct->setCategory(Headers::CCcontainer);
0514         auto cte = contentTransferEncoding();
0515         cte->setEncoding(Headers::CE7Bit);
0516         cte->setDecoded(true);
0517     }
0518 
0519     // Add the new content.
0520     if (prepend) {
0521         d->multipartContents.prepend(c);
0522     } else {
0523         d->multipartContents.append(c);
0524     }
0525 
0526     if (c->parent() != this) {
0527         // If the content was part of something else, this will remove it from there.
0528         c->setParent(this);
0529     }
0530 }
0531 
0532 void Content::appendContent(Content *c)
0533 {
0534     // This method makes no sense for encapsulated messages
0535     Q_ASSERT(!bodyIsMessage());
0536 
0537     Q_D(Content);
0538     d->multipartContents.append(c);
0539 
0540     if (c->parent() != this) {
0541         // If the content was part of something else, this will remove it from there.
0542         c->setParent(this);
0543     }
0544 }
0545 
0546 void Content::prependContent(Content *c)
0547 {
0548     // This method makes no sense for encapsulated messages
0549     Q_ASSERT(!bodyIsMessage());
0550 
0551     Q_D(Content);
0552     d->multipartContents.prepend(c);
0553 
0554     if (c->parent() != this) {
0555         // If the content was part of something else, this will remove it from there.
0556         c->setParent(this);
0557     }
0558 }
0559 
0560 void Content::removeContent(Content *c, bool del)
0561 {
0562     Q_D(Content);
0563     if (d->multipartContents.isEmpty() || !d->multipartContents.contains(c)) {
0564         return;
0565     }
0566 
0567     // This method makes no sense for encapsulated messages.
0568     // Should be covered by the above assert already, though.
0569     Q_ASSERT(!bodyIsMessage());
0570 
0571     d->multipartContents.removeAll(c);
0572     if (del) {
0573         delete c;
0574     } else {
0575         c->d_ptr->parent = nullptr;
0576     }
0577 
0578     // If only one content is left, turn this content into a single-part.
0579     if (d->multipartContents.count() == 1) {
0580         Content *main = d->multipartContents.constFirst();
0581 
0582         // Move all headers from the old subcontent to ourselves.
0583         // NOTE: This also sets the new Content-Type.
0584         const auto headers = main->d_ptr->headers;
0585         for (Headers::Base *h : headers) {
0586             setHeader(h);   // Will remove the old one if present.
0587         }
0588         main->d_ptr->headers.clear();
0589 
0590         // Move the body.
0591         d->body = main->body();
0592 
0593         // Delete the old subcontent.
0594         delete main;
0595         d->multipartContents.clear();
0596     }
0597 }
0598 
0599 Content *Content::takeContent(Content *c)
0600 {
0601     // This method makes no sense for encapsulated messages.
0602     // Should be covered by the above assert already, though.
0603     Q_ASSERT(!bodyIsMessage());
0604 
0605     Q_D(Content);
0606     if (d->multipartContents.isEmpty() || !d->multipartContents.contains(c)) {
0607         return nullptr;
0608     }
0609 
0610     d->multipartContents.removeAll(c);
0611     c->d_ptr->parent = nullptr;
0612     return c;
0613 }
0614 
0615 void Content::changeEncoding(Headers::contentEncoding e)
0616 {
0617     // This method makes no sense for encapsulated messages, they are always 7bit
0618     // encoded.
0619     Q_ASSERT(!bodyIsMessage());
0620 
0621     Headers::ContentTransferEncoding *enc = contentTransferEncoding();
0622     if (enc->encoding() == e) {
0623         // Nothing to do.
0624         return;
0625     }
0626 
0627     if (d_ptr->decodeText(this)) {
0628         // This is textual content.  Textual content is stored decoded.
0629         Q_ASSERT(enc->isDecoded());
0630         enc->setEncoding(e);
0631     } else {
0632         // This is non-textual content.  Re-encode it.
0633         if (e == Headers::CEbase64) {
0634             KCodecs::base64Encode(decodedContent(), d_ptr->body, true);
0635             enc->setEncoding(e);
0636             enc->setDecoded(false);
0637         } else {
0638             // It only makes sense to convert binary stuff to base64.
0639             Q_ASSERT(false);
0640         }
0641     }
0642 }
0643 
0644 QList<Headers::Base *> Content::headers() const { return d_ptr->headers; }
0645 
0646 Headers::Base *Content::headerByType(const char *type) const
0647 {
0648     Q_ASSERT(type  && *type);
0649 
0650     for (Headers::Base *h : std::as_const(d_ptr->headers)) {
0651         if (h->is(type)) {
0652             return h; // Found.
0653         }
0654     }
0655 
0656     return nullptr; // Not found.
0657 }
0658 
0659 QList<Headers::Base *> Content::headersByType(const char *type) const {
0660     Q_ASSERT(type && *type);
0661 
0662     QList<Headers::Base *> result;
0663 
0664     for (Headers::Base *h : std::as_const(d_ptr->headers)) {
0665         if (h->is(type)) {
0666             result << h;
0667         }
0668     }
0669 
0670     return result;
0671 }
0672 
0673 void Content::setHeader(Headers::Base *h)
0674 {
0675     Q_ASSERT(h);
0676     removeHeader(h->type());
0677     appendHeader(h);
0678 }
0679 
0680 void Content::appendHeader(Headers::Base *h)
0681 {
0682     Q_D(Content);
0683     d->headers.append(h);
0684 }
0685 
0686 bool Content::removeHeader(const char *type)
0687 {
0688     Q_D(Content);
0689     const auto endIt = d->headers.end();
0690     for (auto it = d->headers.begin(); it != endIt; ++it) {
0691         if ((*it)->is(type)) {
0692             delete(*it);
0693             d->headers.erase(it);
0694             return true;
0695         }
0696     }
0697 
0698     return false;
0699 }
0700 
0701 bool Content::hasHeader(const char* type) const
0702 {
0703     return headerByType(type) != nullptr;
0704 }
0705 
0706 int Content::size()
0707 {
0708     int ret = d_ptr->body.length();
0709 
0710     if (contentTransferEncoding()->encoding() == Headers::CEbase64) {
0711         KCodecs::Codec *codec = KCodecs::Codec::codecForName("base64");
0712         return codec->maxEncodedSizeFor(ret);
0713     }
0714 
0715     // Not handling quoted-printable here since that requires actually
0716     // converting the content, and that is O(size_of_content).
0717     // For quoted-printable, this is only an approximate size.
0718 
0719     return ret;
0720 }
0721 
0722 int Content::storageSize() const
0723 {
0724     const Q_D(Content);
0725     int s = d->head.size();
0726 
0727     if (d->contents().isEmpty()) {
0728         s += d->body.size();
0729     } else {
0730 
0731         // FIXME: This should take into account the boundary headers that are added in
0732         //        encodedContent!
0733         const auto contents = d->contents();
0734         for (Content *c : contents) {
0735             s += c->storageSize();
0736         }
0737     }
0738 
0739     return s;
0740 }
0741 
0742 bool ContentPrivate::decodeText(Content *q)
0743 {
0744     Headers::ContentTransferEncoding *enc = q->contentTransferEncoding();
0745 
0746     if (!q->contentType()->isText()) {
0747         return false; //non textual data cannot be decoded here => use decodedContent() instead
0748     }
0749     if (enc->isDecoded()) {
0750         return true; //nothing to do
0751     }
0752 
0753     switch (enc->encoding()) {
0754     case Headers::CEbase64 :
0755         body = KCodecs::base64Decode(body);
0756         break;
0757     case Headers::CEquPr :
0758         body = KCodecs::quotedPrintableDecode(body);
0759         break;
0760     case Headers::CEuuenc :
0761         body = KCodecs::uudecode(body);
0762         break;
0763     case Headers::CEbinary :
0764         // nothing to decode
0765     default :
0766         break;
0767     }
0768     if (!body.endsWith("\n")) {
0769         body.append("\n");
0770     }
0771     enc->setDecoded(true);
0772     return true;
0773 }
0774 
0775 QByteArray Content::defaultCharset()
0776 {
0777     return KMime::cachedCharset(QByteArrayLiteral("ISO-8859-1"));
0778 }
0779 
0780 Content *KMime::Content::content(const ContentIndex &index) const
0781 {
0782     if (!index.isValid()) {
0783         return const_cast<KMime::Content *>(this);
0784     }
0785     ContentIndex idx = index;
0786     unsigned int i = idx.pop() - 1; // one-based -> zero-based index
0787     if (i < static_cast<unsigned int>(d_ptr->contents().size())) {
0788         return d_ptr->contents().at(i)->content(idx);
0789     } else {
0790         return nullptr;
0791     }
0792 }
0793 
0794 ContentIndex KMime::Content::indexForContent(Content *content) const
0795 {
0796     int i = d_ptr->contents().indexOf(content);
0797     if (i >= 0) {
0798         ContentIndex ci;
0799         ci.push(i + 1);   // zero-based -> one-based index
0800         return ci;
0801     }
0802     // not found, we need to search recursively
0803     for (int i = 0; i < d_ptr->contents().size(); ++i) {
0804         ContentIndex ci = d_ptr->contents().at(i)->indexForContent(content);
0805         if (ci.isValid()) {
0806             // found it
0807             ci.push(i + 1);   // zero-based -> one-based index
0808             return ci;
0809         }
0810     }
0811     return {}; // not found
0812 }
0813 
0814 bool Content::isTopLevel() const
0815 {
0816     return d_ptr->parent == nullptr;
0817 }
0818 
0819 void Content::setParent(Content *parent)
0820 {
0821     // Make sure the Content is only in the contents list of one parent object
0822     Content *oldParent = d_ptr->parent;
0823     if (oldParent) {
0824         if (!oldParent->contents().isEmpty() && oldParent->contents().contains(this)) {
0825             oldParent->takeContent(this);
0826         }
0827     }
0828 
0829     d_ptr->parent = parent;
0830     if (parent) {
0831         if (!parent->contents().isEmpty() && !parent->contents().contains(this)) {
0832             parent->appendContent(this);
0833         }
0834     }
0835 }
0836 
0837 Content *Content::parent() const
0838 {
0839     return d_ptr->parent;
0840 }
0841 
0842 Content *Content::topLevel() const
0843 {
0844     auto top = const_cast<Content *>(this);
0845     Content *c = parent();
0846     while (c) {
0847         top = c;
0848         c = c->parent();
0849     }
0850 
0851     return top;
0852 }
0853 
0854 ContentIndex Content::index() const
0855 {
0856     Content *top = topLevel();
0857     if (top) {
0858         return top->indexForContent(const_cast<Content *>(this));
0859     }
0860 
0861     return indexForContent(const_cast<Content *>(this));
0862 }
0863 
0864 Message::Ptr Content::bodyAsMessage() const
0865 {
0866     if (bodyIsMessage() && d_ptr->bodyAsMessage) {
0867         return d_ptr->bodyAsMessage;
0868     } else {
0869       return {};
0870     }
0871 }
0872 
0873 bool Content::bodyIsMessage() const
0874 {
0875     // Use const_case here to work around API issue that neither header() nor hasHeader() are
0876     // const, even though they should be
0877     return const_cast<Content *>(this)->header<Headers::ContentType>(false) &&
0878            const_cast<Content *>(this)->header<Headers::ContentType>(true)
0879            ->mimeType().toLower() == "message/rfc822";
0880 }
0881 
0882 // @cond PRIVATE
0883 #define kmime_mk_header_accessor( type, method ) \
0884     Headers::type *Content::method( bool create ) { \
0885         return header<Headers::type>( create ); \
0886     }
0887 
0888 kmime_mk_header_accessor(ContentType, contentType)
0889 kmime_mk_header_accessor(ContentTransferEncoding, contentTransferEncoding)
0890 kmime_mk_header_accessor(ContentDisposition, contentDisposition)
0891 kmime_mk_header_accessor(ContentDescription, contentDescription)
0892 kmime_mk_header_accessor(ContentLocation, contentLocation)
0893 kmime_mk_header_accessor(ContentID, contentID)
0894 
0895 #undef kmime_mk_header_accessor
0896 // @endcond
0897 
0898 void ContentPrivate::clearBodyMessage()
0899 {
0900     bodyAsMessage.reset();
0901 }
0902 
0903 QList<Content *> ContentPrivate::contents() const {
0904     Q_ASSERT(multipartContents.isEmpty() || !bodyAsMessage);
0905     if (bodyAsMessage) {
0906       return QList<Content *>() << bodyAsMessage.data();
0907     } else {
0908         return multipartContents;
0909     }
0910 }
0911 
0912 bool ContentPrivate::parseUuencoded(Content *q)
0913 {
0914     Parser::UUEncoded uup(body, KMime::extractHeader(head, "Subject"));
0915     if (!uup.parse()) {
0916         return false; // Parsing failed.
0917     }
0918 
0919     Headers::ContentType *ct = q->contentType();
0920     ct->clear();
0921 
0922     if (uup.isPartial()) {
0923         // This seems to be only a part of the message, so we treat it as "message/partial".
0924         ct->setMimeType("message/partial");
0925         //ct->setId( uniqueString() ); not needed yet
0926         ct->setPartialParams(uup.partialCount(), uup.partialNumber());
0927         q->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
0928     } else {
0929         // This is a complete message, so treat it as "multipart/mixed".
0930         const auto prevBody = body;
0931         body.clear();
0932         ct->setMimeType("multipart/mixed");
0933         ct->setBoundary(multiPartBoundary());
0934         ct->setCategory(Headers::CCcontainer);
0935         auto cte = q->contentTransferEncoding();
0936         cte->setEncoding(Headers::CE7Bit);
0937         cte->setDecoded(true);
0938 
0939         // Add the plain text part first.
0940         Q_ASSERT(multipartContents.isEmpty());
0941         {
0942             auto c = new Content(q);
0943             c->contentType()->setMimeType("text/plain");
0944             c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
0945             c->setBody(uup.textPart());
0946             multipartContents.append(c);
0947         }
0948 
0949         // Now add each of the binary parts as sub-Contents.
0950         for (int i = 0; i < uup.binaryParts().count(); ++i) {
0951             auto c = new Content(q);
0952             c->contentType()->setMimeType(uup.mimeTypes().at(i));
0953             c->contentType()->setName(QLatin1String(uup.filenames().at(i)), QByteArray(/*charset*/));
0954             c->contentTransferEncoding()->setEncoding(Headers::CEuuenc);
0955             c->contentTransferEncoding()->setDecoded(false);
0956             c->contentDisposition()->setDisposition(Headers::CDattachment);
0957             c->contentDisposition()->setFilename(QLatin1String(uup.filenames().at(i)));
0958             // uup.binaryParts().at(i) does no longer have the uuencode header, which makes KCodecs fail since 5c66308c4786ef7fbf77b0e306e73f7d4ac3431b
0959             c->setBody(prevBody);
0960             c->changeEncoding(Headers::CEbase64);   // Convert to base64.
0961             multipartContents.append(c);
0962         }
0963     }
0964 
0965     return true; // Parsing successful.
0966 }
0967 
0968 bool ContentPrivate::parseYenc(Content *q)
0969 {
0970     Parser::YENCEncoded yenc(body);
0971     if (!yenc.parse()) {
0972         return false; // Parsing failed.
0973     }
0974 
0975     Headers::ContentType *ct = q->contentType();
0976     ct->clear();
0977 
0978     if (yenc.isPartial()) {
0979         // Assume there is exactly one decoded part.  Treat this as "message/partial".
0980         ct->setMimeType("message/partial");
0981         //ct->setId( uniqueString() ); not needed yet
0982         ct->setPartialParams(yenc.partialCount(), yenc.partialNumber());
0983         q->contentTransferEncoding()->setEncoding(Headers::CEbinary);
0984         q->changeEncoding(Headers::CEbase64);   // Convert to base64.
0985     } else {
0986         // This is a complete message, so treat it as "multipart/mixed".
0987         body.clear();
0988         ct->setMimeType("multipart/mixed");
0989         ct->setBoundary(multiPartBoundary());
0990         ct->setCategory(Headers::CCcontainer);
0991         auto cte = q->contentTransferEncoding();
0992         cte->setEncoding(Headers::CE7Bit);
0993         cte->setDecoded(true);
0994 
0995         // Add the plain text part first.
0996         Q_ASSERT(multipartContents.isEmpty());
0997         {
0998             auto c = new Content(q);
0999             c->contentType()->setMimeType("text/plain");
1000             c->contentTransferEncoding()->setEncoding(Headers::CE7Bit);
1001             c->setBody(yenc.textPart());
1002             multipartContents.append(c);
1003         }
1004 
1005         // Now add each of the binary parts as sub-Contents.
1006         for (int i = 0; i < yenc.binaryParts().count(); i++) {
1007             auto c = new Content(q);
1008             c->contentType()->setMimeType(yenc.mimeTypes().at(i));
1009             c->contentType()->setName(QLatin1String(yenc.filenames().at(i)), QByteArray(/*charset*/));
1010             c->contentTransferEncoding()->setEncoding(Headers::CEbinary);
1011             c->contentDisposition()->setDisposition(Headers::CDattachment);
1012             c->contentDisposition()->setFilename(QLatin1String(yenc.filenames().at(i)));
1013             c->setBody(yenc.binaryParts().at(i));     // Yenc bodies are binary.
1014             c->changeEncoding(Headers::CEbase64);   // Convert to base64.
1015             multipartContents.append(c);
1016         }
1017     }
1018 
1019     return true; // Parsing successful.
1020 }
1021 
1022 bool ContentPrivate::parseMultipart(Content *q)
1023 {
1024     const Headers::ContentType *ct = q->contentType();
1025     const QByteArray boundary = ct->boundary();
1026     if (boundary.isEmpty()) {
1027         return false; // Parsing failed; invalid multipart content.
1028     }
1029     Parser::MultiPart mpp(body, boundary);
1030     if (!mpp.parse()) {
1031         return false; // Parsing failed.
1032     }
1033 
1034     preamble = mpp.preamble();
1035     epilogue = mpp.epilouge();
1036 
1037     // Determine the category of the subparts (used in attachments()).
1038     Headers::contentCategory cat;
1039     if (ct->isSubtype("alternative")) {
1040         cat = Headers::CCalternativePart;
1041     } else {
1042         cat = Headers::CCmixedPart; // Default to "mixed".
1043     }
1044 
1045     // Create a sub-Content for every part.
1046     Q_ASSERT(multipartContents.isEmpty());
1047     body.clear();
1048     const auto parts = mpp.parts();
1049     for (const QByteArray &part : parts) {
1050         auto c = new Content(q);
1051         c->setContent(part);
1052         c->setFrozen(frozen);
1053         c->parse();
1054         c->contentType()->setCategory(cat);
1055         multipartContents.append(c);
1056     }
1057 
1058     return true; // Parsing successful.
1059 }
1060 
1061 } // namespace KMime