diff options
Diffstat (limited to 'framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp')
-rw-r--r-- | framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp b/framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp new file mode 100644 index 00000000..914298b9 --- /dev/null +++ b/framework/src/domain/mime/mimetreeparser/objecttreeparser.cpp | |||
@@ -0,0 +1,549 @@ | |||
1 | /* | ||
2 | objecttreeparser.cpp | ||
3 | |||
4 | This file is part of KMail, the KDE mail client. | ||
5 | Copyright (c) 2003 Marc Mutz <mutz@kde.org> | ||
6 | Copyright (C) 2002-2004 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net | ||
7 | Copyright (c) 2009 Andras Mantia <andras@kdab.net> | ||
8 | Copyright (c) 2015 Sandro Knauß <sknauss@kde.org> | ||
9 | |||
10 | KMail is free software; you can redistribute it and/or modify it | ||
11 | under the terms of the GNU General Public License, version 2, as | ||
12 | published by the Free Software Foundation. | ||
13 | |||
14 | KMail is distributed in the hope that it will be useful, but | ||
15 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
17 | General Public License for more details. | ||
18 | |||
19 | You should have received a copy of the GNU General Public License | ||
20 | along with this program; if not, write to the Free Software | ||
21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
22 | |||
23 | In addition, as a special exception, the copyright holders give | ||
24 | permission to link the code of this program with any edition of | ||
25 | the Qt library by Trolltech AS, Norway (or with modified versions | ||
26 | of Qt that use the same license as Qt), and distribute linked | ||
27 | combinations including the two. You must obey the GNU General | ||
28 | Public License in all respects for all of the code used other than | ||
29 | Qt. If you modify this file, you may extend this exception to | ||
30 | your version of the file, but you are not obligated to do so. If | ||
31 | you do not wish to do so, delete this exception statement from | ||
32 | your version. | ||
33 | */ | ||
34 | |||
35 | // MessageViewer includes | ||
36 | |||
37 | #include "objecttreeparser.h" | ||
38 | |||
39 | #include "bodypartformatterbasefactory.h" | ||
40 | #include "nodehelper.h" | ||
41 | #include "messagepart.h" | ||
42 | #include "partnodebodypart.h" | ||
43 | |||
44 | #include "mimetreeparser_debug.h" | ||
45 | |||
46 | #include "utils.h" | ||
47 | #include "bodypartformatter.h" | ||
48 | #include "util.h" | ||
49 | |||
50 | #include <KMime/Headers> | ||
51 | #include <KMime/Message> | ||
52 | |||
53 | // KDE includes | ||
54 | |||
55 | // Qt includes | ||
56 | #include <QByteArray> | ||
57 | #include <QTextCodec> | ||
58 | #include <QUrl> | ||
59 | #include <QMimeDatabase> | ||
60 | |||
61 | using namespace MimeTreeParser; | ||
62 | |||
63 | |||
64 | ObjectTreeParser::ObjectTreeParser() | ||
65 | : mNodeHelper(nullptr), | ||
66 | mTopLevelContent(nullptr), | ||
67 | mShowOnlyOneMimePart(false), | ||
68 | mHasPendingAsyncJobs(false), | ||
69 | mAllowAsync(false) | ||
70 | { | ||
71 | init(); | ||
72 | } | ||
73 | |||
74 | ObjectTreeParser::ObjectTreeParser(MimeTreeParser::NodeHelper *nodeHelper, | ||
75 | bool showOnlyOneMimePart) | ||
76 | : mNodeHelper(nodeHelper), | ||
77 | mTopLevelContent(nullptr), | ||
78 | mShowOnlyOneMimePart(showOnlyOneMimePart), | ||
79 | mHasPendingAsyncJobs(false), | ||
80 | mAllowAsync(false) | ||
81 | { | ||
82 | init(); | ||
83 | } | ||
84 | |||
85 | void ObjectTreeParser::init() | ||
86 | { | ||
87 | if (!mNodeHelper) { | ||
88 | mNodeHelper = new NodeHelper(); | ||
89 | mDeleteNodeHelper = true; | ||
90 | } else { | ||
91 | mDeleteNodeHelper = false; | ||
92 | } | ||
93 | } | ||
94 | |||
95 | ObjectTreeParser::~ObjectTreeParser() | ||
96 | { | ||
97 | if (mDeleteNodeHelper) { | ||
98 | delete mNodeHelper; | ||
99 | mNodeHelper = nullptr; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | void ObjectTreeParser::setAllowAsync(bool allow) | ||
104 | { | ||
105 | Q_ASSERT(!mHasPendingAsyncJobs); | ||
106 | mAllowAsync = allow; | ||
107 | } | ||
108 | |||
109 | bool ObjectTreeParser::allowAsync() const | ||
110 | { | ||
111 | return mAllowAsync; | ||
112 | } | ||
113 | |||
114 | bool ObjectTreeParser::hasPendingAsyncJobs() const | ||
115 | { | ||
116 | return mHasPendingAsyncJobs; | ||
117 | } | ||
118 | |||
119 | QString ObjectTreeParser::plainTextContent() const | ||
120 | { | ||
121 | return mPlainTextContent; | ||
122 | } | ||
123 | |||
124 | QString ObjectTreeParser::htmlContent() const | ||
125 | { | ||
126 | return mHtmlContent; | ||
127 | } | ||
128 | |||
129 | static void print(KMime::Content *node, const QString prefix = {}) | ||
130 | { | ||
131 | QByteArray mediaType("text"); | ||
132 | QByteArray subType("plain"); | ||
133 | if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && | ||
134 | !node->contentType()->subType().isEmpty()) { | ||
135 | mediaType = node->contentType()->mediaType(); | ||
136 | subType = node->contentType()->subType(); | ||
137 | } | ||
138 | qWarning() << prefix << "!" << mediaType << subType; | ||
139 | for (const auto c: node->contents()) { | ||
140 | print(c, prefix + QLatin1String(" ")); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | static void print(const MessagePart &messagePart, const QByteArray pre = {}) | ||
145 | { | ||
146 | qWarning() << pre << "#" << messagePart.metaObject()->className(); | ||
147 | for (const auto &p: messagePart.subParts()) { | ||
148 | print(*p, pre + " "); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | void ObjectTreeParser::print() | ||
153 | { | ||
154 | if (mTopLevelContent) { | ||
155 | ::print(mTopLevelContent); | ||
156 | } | ||
157 | if (mParsedPart) { | ||
158 | ::print(*mParsedPart); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | static KMime::Content *find(KMime::Content *node, const std::function<bool(KMime::Content *)> &select) | ||
163 | { | ||
164 | QByteArray mediaType("text"); | ||
165 | QByteArray subType("plain"); | ||
166 | if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && | ||
167 | !node->contentType()->subType().isEmpty()) { | ||
168 | mediaType = node->contentType()->mediaType(); | ||
169 | subType = node->contentType()->subType(); | ||
170 | } | ||
171 | if (select(node)) { | ||
172 | return node; | ||
173 | } | ||
174 | for (const auto c: node->contents()) { | ||
175 | if (const auto n = find(c, select)) { | ||
176 | return n; | ||
177 | } | ||
178 | } | ||
179 | return nullptr; | ||
180 | } | ||
181 | |||
182 | |||
183 | KMime::Content *ObjectTreeParser::find(const std::function<bool(KMime::Content *)> &select) | ||
184 | { | ||
185 | return ::find(mTopLevelContent, select); | ||
186 | } | ||
187 | |||
188 | /* | ||
189 | * Collect message parts bottom up. | ||
190 | * Filter to avoid evaluating a subtree. | ||
191 | * Select parts to include it in the result set. Selecting a part in a branch will keep any parent parts from being selected. | ||
192 | */ | ||
193 | static QVector<MessagePart::Ptr> collect(MessagePart::Ptr start, const std::function<bool(const MessagePartPtr &)> &filter, const std::function<bool(const MessagePartPtr &)> &select) | ||
194 | { | ||
195 | MessagePartPtr ptr = start.dynamicCast<MessagePart>(); | ||
196 | Q_ASSERT(ptr); | ||
197 | if (!filter(ptr)) { | ||
198 | return {}; | ||
199 | } | ||
200 | |||
201 | QVector<MessagePart::Ptr> list; | ||
202 | if (ptr) { | ||
203 | for (const auto &p: ptr->subParts()) { | ||
204 | list << ::collect(p, filter, select); | ||
205 | } | ||
206 | } | ||
207 | //Don't consider this part if we already selected a subpart | ||
208 | if (list.isEmpty()) { | ||
209 | if (select(ptr)) { | ||
210 | list << start; | ||
211 | } | ||
212 | } | ||
213 | return list; | ||
214 | } | ||
215 | |||
216 | static bool isAttachment(MessagePart::Ptr part) | ||
217 | { | ||
218 | //TODO | ||
219 | // show everything but the first text/plain body as attachment | ||
220 | if (part->disposition() == MessagePart::Inline) { | ||
221 | return false; | ||
222 | } | ||
223 | if (part->disposition() == MessagePart::Attachment) { | ||
224 | return true; | ||
225 | } | ||
226 | // text/* w/o filename parameter should go inline | ||
227 | if (part->node()) { | ||
228 | const auto ct = part->node()->contentType(false); | ||
229 | if (ct && ct->isText() && ct->name().trimmed().isEmpty() && part->filename().trimmed().isEmpty()) { | ||
230 | return false; | ||
231 | } | ||
232 | return true; | ||
233 | } | ||
234 | return false; | ||
235 | } | ||
236 | |||
237 | QVector<MessagePart::Ptr> ObjectTreeParser::collectContentParts() | ||
238 | { | ||
239 | QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart, | ||
240 | [] (const MessagePartPtr &part) { | ||
241 | // return p->type() != "EncapsulatedPart"; | ||
242 | return true; | ||
243 | }, | ||
244 | [] (const MessagePartPtr &part) { | ||
245 | if (const auto attachment = dynamic_cast<MimeTreeParser::AttachmentMessagePart*>(part.data())) { | ||
246 | return false; | ||
247 | } else if (const auto text = dynamic_cast<MimeTreeParser::TextMessagePart*>(part.data())) { | ||
248 | auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart*>(text->parentPart()); | ||
249 | if (enc && enc->error()) { | ||
250 | return false; | ||
251 | } | ||
252 | return true; | ||
253 | } else if (const auto alternative = dynamic_cast<MimeTreeParser::AlternativeMessagePart*>(part.data())) { | ||
254 | return true; | ||
255 | } else if (const auto html = dynamic_cast<MimeTreeParser::HtmlMessagePart*>(part.data())) { | ||
256 | return true; | ||
257 | } else if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart*>(part.data())) { | ||
258 | if (enc->error()) { | ||
259 | return true; | ||
260 | } | ||
261 | //If we have a textpart with encrypted and unencrypted subparts we want to return the textpart | ||
262 | if (dynamic_cast<MimeTreeParser::TextMessagePart*>(enc->parentPart())) { | ||
263 | return false; | ||
264 | } | ||
265 | } else if (const auto sig = dynamic_cast<MimeTreeParser::SignedMessagePart*>(part.data())) { | ||
266 | //Signatures without subparts already contain the text | ||
267 | return !sig->hasSubParts(); | ||
268 | } | ||
269 | return false; | ||
270 | }); | ||
271 | return contentParts; | ||
272 | } | ||
273 | |||
274 | QVector<MessagePart::Ptr> ObjectTreeParser::collectAttachmentParts() | ||
275 | { | ||
276 | QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart, | ||
277 | [] (const MessagePartPtr &part) { | ||
278 | return true; | ||
279 | }, | ||
280 | [] (const MessagePartPtr &part) { | ||
281 | if (const auto attachment = dynamic_cast<MimeTreeParser::AttachmentMessagePart*>(part.data())) { | ||
282 | return true; | ||
283 | } | ||
284 | return false; | ||
285 | }); | ||
286 | return contentParts; | ||
287 | } | ||
288 | |||
289 | void ObjectTreeParser::decryptParts() | ||
290 | { | ||
291 | ::collect(mParsedPart, | ||
292 | [] (const MessagePartPtr &part) { return true; }, | ||
293 | [] (const MessagePartPtr &part) { | ||
294 | if (const auto enc = dynamic_cast<MimeTreeParser::EncryptedMessagePart*>(part.data())) { | ||
295 | enc->startDecryption(); | ||
296 | } | ||
297 | return false; | ||
298 | }); | ||
299 | print(); | ||
300 | ::collect(mParsedPart, | ||
301 | [] (const MessagePartPtr &part) { return true; }, | ||
302 | [] (const MessagePartPtr &part) { | ||
303 | if (const auto enc = dynamic_cast<MimeTreeParser::SignedMessagePart*>(part.data())) { | ||
304 | enc->startVerification(); | ||
305 | } | ||
306 | return false; | ||
307 | }); | ||
308 | } | ||
309 | |||
310 | void ObjectTreeParser::importCertificates() | ||
311 | { | ||
312 | QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart, | ||
313 | [] (const MessagePartPtr &part) { return true; }, | ||
314 | [] (const MessagePartPtr &part) { | ||
315 | if (const auto cert = dynamic_cast<MimeTreeParser::CertMessagePart*>(part.data())) { | ||
316 | cert->import(); | ||
317 | } | ||
318 | return false; | ||
319 | }); | ||
320 | } | ||
321 | |||
322 | |||
323 | QString ObjectTreeParser::resolveCidLinks(const QString &html) | ||
324 | { | ||
325 | auto text = html; | ||
326 | const auto rx = QRegExp(QLatin1String("(src)\\s*=\\s*(\"|')(cid:[^\"']+)\\2")); | ||
327 | int pos = 0; | ||
328 | while ((pos = rx.indexIn(text, pos)) != -1) { | ||
329 | const auto link = QUrl(rx.cap(3)); | ||
330 | pos += rx.matchedLength(); | ||
331 | auto cid = link.path(); | ||
332 | auto mailMime = const_cast<KMime::Content *>(find([=] (KMime::Content *c) { | ||
333 | if (!c || !c->contentID(false)) { | ||
334 | return false; | ||
335 | } | ||
336 | return QString::fromLatin1(c->contentID(false)->identifier()) == cid; | ||
337 | })); | ||
338 | if (mailMime) { | ||
339 | const auto ct = mailMime->contentType(false); | ||
340 | if (!ct) { | ||
341 | qWarning() << "No content type, skipping"; | ||
342 | continue; | ||
343 | } | ||
344 | QMimeDatabase mimeDb; | ||
345 | const auto mimetype = mimeDb.mimeTypeForName(QString::fromLatin1(ct->mimeType())).name(); | ||
346 | if (mimetype.startsWith(QLatin1String("image/"))) { | ||
347 | //We reencode to base64 below. | ||
348 | const auto data = mailMime->decodedContent(); | ||
349 | if (data.isEmpty()) { | ||
350 | qWarning() << "Attachment is empty."; | ||
351 | continue; | ||
352 | } | ||
353 | text.replace(rx.cap(0), QString::fromLatin1("src=\"data:%1;base64,%2\"").arg(mimetype, QString::fromLatin1(data.toBase64()))); | ||
354 | } | ||
355 | } else { | ||
356 | qWarning() << "Failed to find referenced attachment: " << cid; | ||
357 | } | ||
358 | } | ||
359 | return text; | ||
360 | } | ||
361 | |||
362 | //----------------------------------------------------------------------------- | ||
363 | |||
364 | void ObjectTreeParser::parseObjectTree(const QByteArray &mimeMessage) | ||
365 | { | ||
366 | const auto mailData = KMime::CRLFtoLF(mimeMessage); | ||
367 | mMsg = KMime::Message::Ptr(new KMime::Message); | ||
368 | mMsg->setContent(mailData); | ||
369 | mMsg->parse(); | ||
370 | parseObjectTree(mMsg.data()); | ||
371 | } | ||
372 | |||
373 | void ObjectTreeParser::parseObjectTree(KMime::Content *node) | ||
374 | { | ||
375 | mTopLevelContent = node; | ||
376 | mParsedPart = parseObjectTreeInternal(node, showOnlyOneMimePart()); | ||
377 | |||
378 | //Gather plaintext and html content | ||
379 | if (mParsedPart) { | ||
380 | //Find relevant plaintext parts and set plaintext | ||
381 | if (auto mp = toplevelTextNode(mParsedPart)) { | ||
382 | if (auto _mp = mp.dynamicCast<TextMessagePart>()) { | ||
383 | mPlainTextContent += _mp->mNode->decodedText(); | ||
384 | mPlainTextContentCharset += NodeHelper::charset(_mp->mNode); | ||
385 | } else if (auto _mp = mp.dynamicCast<AlternativeMessagePart>()) { | ||
386 | if (_mp->mChildNodes.contains(Util::MultipartPlain)) { | ||
387 | mPlainTextContent += _mp->mChildNodes[Util::MultipartPlain]->decodedText(); | ||
388 | mPlainTextContentCharset += NodeHelper::charset(_mp->mChildNodes[Util::MultipartPlain]); | ||
389 | } | ||
390 | } | ||
391 | } | ||
392 | |||
393 | //Find html parts and copy content | ||
394 | QVector<MessagePart::Ptr> contentParts = ::collect(mParsedPart, | ||
395 | [] (const MessagePartPtr &part) { | ||
396 | return true; | ||
397 | }, | ||
398 | [] (const MessagePartPtr &part) { | ||
399 | if (const auto html = dynamic_cast<MimeTreeParser::HtmlMessagePart*>(part.data())) { | ||
400 | return true; | ||
401 | } | ||
402 | return false; | ||
403 | }); | ||
404 | for (const auto &part : contentParts) { | ||
405 | mHtmlContent += part->text(); | ||
406 | mHtmlContentCharset = part->charset(); | ||
407 | } | ||
408 | } | ||
409 | } | ||
410 | |||
411 | MessagePartPtr ObjectTreeParser::parsedPart() const | ||
412 | { | ||
413 | return mParsedPart; | ||
414 | } | ||
415 | |||
416 | MessagePartPtr ObjectTreeParser::processType(KMime::Content *node, const QByteArray &mediaType, const QByteArray &subType, bool onlyOneMimePart) | ||
417 | { | ||
418 | static MimeTreeParser::BodyPartFormatterBaseFactory factory; | ||
419 | const auto sub = factory.subtypeRegistry(mediaType.constData()); | ||
420 | auto range = sub.equal_range(subType.constData()); | ||
421 | for (auto it = range.first; it != range.second; ++it) { | ||
422 | const auto formatter = (*it).second; | ||
423 | if (!formatter) { | ||
424 | continue; | ||
425 | } | ||
426 | PartNodeBodyPart part(this, mTopLevelContent, node, mNodeHelper); | ||
427 | if (const MessagePart::Ptr result = formatter->process(part)) { | ||
428 | return result; | ||
429 | } | ||
430 | } | ||
431 | return {}; | ||
432 | } | ||
433 | |||
434 | MessagePart::Ptr ObjectTreeParser::parseObjectTreeInternal(KMime::Content *node, bool onlyOneMimePart) | ||
435 | { | ||
436 | if (!node) { | ||
437 | return MessagePart::Ptr(); | ||
438 | } | ||
439 | |||
440 | // reset pending async jobs state (we'll rediscover pending jobs as we go) | ||
441 | mHasPendingAsyncJobs = false; | ||
442 | |||
443 | // reset "processed" flags for... | ||
444 | if (onlyOneMimePart) { | ||
445 | // ... this node and all descendants | ||
446 | mNodeHelper->setNodeUnprocessed(node, false); | ||
447 | if (!node->contents().isEmpty()) { | ||
448 | mNodeHelper->setNodeUnprocessed(node, true); | ||
449 | } | ||
450 | } else if (!node->parent()) { | ||
451 | // ...this node and all it's siblings and descendants | ||
452 | mNodeHelper->setNodeUnprocessed(node, true); | ||
453 | } | ||
454 | |||
455 | const bool isRoot = node->isTopLevel(); | ||
456 | auto parsedPart = MessagePart::Ptr(new MessagePartList(this)); | ||
457 | parsedPart->setIsRoot(isRoot); | ||
458 | KMime::Content *parent = node->parent(); | ||
459 | auto contents = parent ? parent->contents() : KMime::Content::List(); | ||
460 | if (contents.isEmpty()) { | ||
461 | contents.append(node); | ||
462 | } | ||
463 | int i = contents.indexOf(const_cast<KMime::Content *>(node)); | ||
464 | for (; i < contents.size(); ++i) { | ||
465 | node = contents.at(i); | ||
466 | if (mNodeHelper->nodeProcessed(node)) { | ||
467 | continue; | ||
468 | } | ||
469 | |||
470 | QByteArray mediaType("text"); | ||
471 | QByteArray subType("plain"); | ||
472 | if (node->contentType(false) && !node->contentType()->mediaType().isEmpty() && | ||
473 | !node->contentType()->subType().isEmpty()) { | ||
474 | mediaType = node->contentType()->mediaType(); | ||
475 | subType = node->contentType()->subType(); | ||
476 | } | ||
477 | |||
478 | //Try the specific type handler | ||
479 | if (auto mp = processType(node, mediaType, subType, onlyOneMimePart)) { | ||
480 | if (mp) { | ||
481 | parsedPart->appendSubPart(mp); | ||
482 | } | ||
483 | //Fallback to the generic handler | ||
484 | } else if (auto mp = processType(node, mediaType, "*", onlyOneMimePart)) { | ||
485 | if (mp) { | ||
486 | parsedPart->appendSubPart(mp); | ||
487 | } | ||
488 | //Fallback to the default handler | ||
489 | } else { | ||
490 | if (auto mp = defaultHandling(node, onlyOneMimePart)) { | ||
491 | parsedPart->appendSubPart(mp); | ||
492 | } | ||
493 | } | ||
494 | mNodeHelper->setNodeProcessed(node, false); | ||
495 | |||
496 | if (onlyOneMimePart) { | ||
497 | break; | ||
498 | } | ||
499 | } | ||
500 | |||
501 | return parsedPart; | ||
502 | } | ||
503 | |||
504 | MessagePart::Ptr ObjectTreeParser::defaultHandling(KMime::Content *node, bool onlyOneMimePart) | ||
505 | { | ||
506 | if (node->contentType()->mimeType() == QByteArrayLiteral("application/octet-stream") && | ||
507 | (node->contentType()->name().endsWith(QLatin1String("p7m")) || | ||
508 | node->contentType()->name().endsWith(QLatin1String("p7s")) || | ||
509 | node->contentType()->name().endsWith(QLatin1String("p7c")) | ||
510 | )) { | ||
511 | if (auto mp = processType(node, "application", "pkcs7-mime", onlyOneMimePart)) { | ||
512 | return mp; | ||
513 | } | ||
514 | } | ||
515 | |||
516 | const auto mp = AttachmentMessagePart::Ptr(new AttachmentMessagePart(this, node)); | ||
517 | return mp; | ||
518 | } | ||
519 | |||
520 | const QTextCodec *ObjectTreeParser::codecFor(KMime::Content *node) const | ||
521 | { | ||
522 | Q_ASSERT(node); | ||
523 | return mNodeHelper->codec(node); | ||
524 | } | ||
525 | |||
526 | QByteArray ObjectTreeParser::plainTextContentCharset() const | ||
527 | { | ||
528 | return mPlainTextContentCharset; | ||
529 | } | ||
530 | |||
531 | QByteArray ObjectTreeParser::htmlContentCharset() const | ||
532 | { | ||
533 | return mHtmlContentCharset; | ||
534 | } | ||
535 | |||
536 | bool ObjectTreeParser::showOnlyOneMimePart() const | ||
537 | { | ||
538 | return mShowOnlyOneMimePart; | ||
539 | } | ||
540 | |||
541 | void ObjectTreeParser::setShowOnlyOneMimePart(bool show) | ||
542 | { | ||
543 | mShowOnlyOneMimePart = show; | ||
544 | } | ||
545 | |||
546 | MimeTreeParser::NodeHelper *ObjectTreeParser::nodeHelper() const | ||
547 | { | ||
548 | return mNodeHelper; | ||
549 | } | ||