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