diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-05-29 16:17:04 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-06-04 12:57:04 +0200 |
commit | e452707fdfbd61be1e5633b516b653b7337e7865 (patch) | |
tree | 1e1d4b48ebf8d381f292436f2ba04b8763edc5de /framework/src/domain/mime/mimetreeparser/interface.cpp | |
parent | 5a1033bdace740799a6e03389bee30e5a4de5d44 (diff) | |
download | kube-e452707fdfbd61be1e5633b516b653b7337e7865.tar.gz kube-e452707fdfbd61be1e5633b516b653b7337e7865.zip |
Reduced the messagetreeparser to aproximately what we actually require
While in a much more managable state it's still not pretty.
However, further refactoring can now gradually happen as we need to do
further work on it.
Things that should happen eventually:
* Simplify the logic that creates the messageparts (we don't need the whole formatter plugin complexity)
* Get rid of the nodehelper (let the parts hold the necessary data)
* Get rid of partmetadata (let the part handleit)
Diffstat (limited to 'framework/src/domain/mime/mimetreeparser/interface.cpp')
-rw-r--r-- | framework/src/domain/mime/mimetreeparser/interface.cpp | 1195 |
1 files changed, 0 insertions, 1195 deletions
diff --git a/framework/src/domain/mime/mimetreeparser/interface.cpp b/framework/src/domain/mime/mimetreeparser/interface.cpp deleted file mode 100644 index 653789a5..00000000 --- a/framework/src/domain/mime/mimetreeparser/interface.cpp +++ /dev/null | |||
@@ -1,1195 +0,0 @@ | |||
1 | /* | ||
2 | Copyright (c) 2016 Sandro Knauß <knauss@kolabsystems.com> | ||
3 | |||
4 | This library is free software; you can redistribute it and/or modify it | ||
5 | under the terms of the GNU Library General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or (at your | ||
7 | option) any later version. | ||
8 | |||
9 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
10 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
12 | License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU Library General Public License | ||
15 | along with this library; see the file COPYING.LIB. If not, write to the | ||
16 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
17 | 02110-1301, USA. | ||
18 | */ | ||
19 | |||
20 | #include "interface.h" | ||
21 | #include "interface_p.h" | ||
22 | |||
23 | #include "stringhtmlwriter.h" | ||
24 | #include "objecttreesource.h" | ||
25 | |||
26 | #include <QGpgME/KeyListJob> | ||
27 | #include <QGpgME/Protocol> | ||
28 | #include <gpgme++/key.h> | ||
29 | #include <gpgme++/keylistresult.h> | ||
30 | |||
31 | #include <KMime/Content> | ||
32 | #include <otp/objecttreeparser.h> | ||
33 | #include <otp/messagepart.h> | ||
34 | #include <otp/nodehelper.h> | ||
35 | |||
36 | #include <QMimeDatabase> | ||
37 | #include <QMimeType> | ||
38 | #include <QTextCodec> | ||
39 | #include <QDebug> | ||
40 | |||
41 | class MailMimePrivate | ||
42 | { | ||
43 | public: | ||
44 | MailMimePrivate(MailMime *p); | ||
45 | |||
46 | MailMime *q; | ||
47 | KMime::Content *mNode; | ||
48 | std::shared_ptr<MailMime> parent; | ||
49 | }; | ||
50 | |||
51 | MailMimePrivate::MailMimePrivate(MailMime* p) | ||
52 | : q(p) | ||
53 | , mNode(nullptr) | ||
54 | , parent(nullptr) | ||
55 | { | ||
56 | } | ||
57 | |||
58 | |||
59 | MailMime::MailMime() | ||
60 | : d(std::unique_ptr<MailMimePrivate>(new MailMimePrivate(this))) | ||
61 | { | ||
62 | } | ||
63 | |||
64 | QByteArray MailMime::cid() const | ||
65 | { | ||
66 | if (!d->mNode || !d->mNode->contentID()) { | ||
67 | return QByteArray(); | ||
68 | } | ||
69 | return d->mNode->contentID()->identifier(); | ||
70 | } | ||
71 | |||
72 | QByteArray MailMime::charset() const | ||
73 | { | ||
74 | if(!d->mNode || !d->mNode->contentType(false)) { | ||
75 | return QByteArray(); | ||
76 | } | ||
77 | if (d->mNode->contentType(false)) { | ||
78 | return d->mNode->contentType(false)->charset(); | ||
79 | } | ||
80 | return d->mNode->defaultCharset(); | ||
81 | } | ||
82 | |||
83 | bool MailMime::isFirstTextPart() const | ||
84 | { | ||
85 | if (!d->mNode || !d->mNode->topLevel()) { | ||
86 | return false; | ||
87 | } | ||
88 | return (d->mNode->topLevel()->textContent() == d->mNode); | ||
89 | } | ||
90 | |||
91 | bool MailMime::isFirstPart() const | ||
92 | { | ||
93 | if (!d->mNode || !d->mNode->parent()) { | ||
94 | return false; | ||
95 | } | ||
96 | return (d->mNode->parent()->contents().first() == d->mNode); | ||
97 | } | ||
98 | |||
99 | bool MailMime::isTopLevelPart() const | ||
100 | { | ||
101 | if (!d->mNode) { | ||
102 | return false; | ||
103 | } | ||
104 | return (d->mNode->topLevel() == d->mNode); | ||
105 | } | ||
106 | |||
107 | MailMime::Disposition MailMime::disposition() const | ||
108 | { | ||
109 | if (!d->mNode) { | ||
110 | return Invalid; | ||
111 | } | ||
112 | const auto cd = d->mNode->contentDisposition(false); | ||
113 | if (!cd) { | ||
114 | return Invalid; | ||
115 | } | ||
116 | switch (cd->disposition()){ | ||
117 | case KMime::Headers::CDinline: | ||
118 | return Inline; | ||
119 | case KMime::Headers::CDattachment: | ||
120 | return Attachment; | ||
121 | default: | ||
122 | return Invalid; | ||
123 | } | ||
124 | } | ||
125 | |||
126 | QString MailMime::filename() const | ||
127 | { | ||
128 | if (!d->mNode) { | ||
129 | return QString(); | ||
130 | } | ||
131 | const auto cd = d->mNode->contentDisposition(false); | ||
132 | if (!cd) { | ||
133 | return QString(); | ||
134 | } | ||
135 | return cd->filename(); | ||
136 | } | ||
137 | |||
138 | QMimeType MailMime::mimetype() const | ||
139 | { | ||
140 | if (!d->mNode) { | ||
141 | return QMimeType(); | ||
142 | } | ||
143 | |||
144 | const auto ct = d->mNode->contentType(false); | ||
145 | if (!ct) { | ||
146 | return QMimeType(); | ||
147 | } | ||
148 | |||
149 | QMimeDatabase mimeDb; | ||
150 | return mimeDb.mimeTypeForName(ct->mimeType()); | ||
151 | } | ||
152 | |||
153 | static KMime::Headers::ContentType *contentType(KMime::Content *node) | ||
154 | { | ||
155 | if (node) { | ||
156 | return node->contentType(false); | ||
157 | } | ||
158 | return nullptr; | ||
159 | } | ||
160 | |||
161 | bool MailMime::isText() const | ||
162 | { | ||
163 | if (auto ct = contentType(d->mNode)) { | ||
164 | return ct->isText(); | ||
165 | } | ||
166 | return false; | ||
167 | } | ||
168 | |||
169 | MailMime::Ptr MailMime::parent() const | ||
170 | { | ||
171 | if (!d->parent) { | ||
172 | d->parent = std::shared_ptr<MailMime>(new MailMime()); | ||
173 | d->parent->d->mNode = d->mNode->parent(); | ||
174 | } | ||
175 | return d->parent; | ||
176 | } | ||
177 | |||
178 | QByteArray MailMime::decodedContent() const | ||
179 | { | ||
180 | if (!d->mNode) { | ||
181 | return QByteArray(); | ||
182 | } | ||
183 | return d->mNode->decodedContent(); | ||
184 | } | ||
185 | |||
186 | class KeyPrivate | ||
187 | { | ||
188 | public: | ||
189 | Key *q; | ||
190 | GpgME::Key mKey; | ||
191 | QByteArray mKeyID; | ||
192 | }; | ||
193 | |||
194 | Key::Key() | ||
195 | :d(std::unique_ptr<KeyPrivate>(new KeyPrivate)) | ||
196 | { | ||
197 | d->q = this; | ||
198 | } | ||
199 | |||
200 | |||
201 | Key::Key(KeyPrivate *d_ptr) | ||
202 | :d(std::unique_ptr<KeyPrivate>(d_ptr)) | ||
203 | { | ||
204 | d->q = this; | ||
205 | } | ||
206 | |||
207 | Key::~Key() | ||
208 | { | ||
209 | |||
210 | } | ||
211 | |||
212 | QString Key::keyid() const | ||
213 | { | ||
214 | if (!d->mKey.isNull()) { | ||
215 | return d->mKey.keyID(); | ||
216 | } | ||
217 | |||
218 | return d->mKeyID; | ||
219 | } | ||
220 | |||
221 | QString Key::name() const | ||
222 | { | ||
223 | //FIXME: is this the correct way to get the primary UID? | ||
224 | if (!d->mKey.isNull()) { | ||
225 | return d->mKey.userID(0).name(); | ||
226 | } | ||
227 | |||
228 | return QString(); | ||
229 | } | ||
230 | |||
231 | QString Key::email() const | ||
232 | { | ||
233 | if (!d->mKey.isNull()) { | ||
234 | return d->mKey.userID(0).email(); | ||
235 | } | ||
236 | return QString(); | ||
237 | } | ||
238 | |||
239 | QString Key::comment() const | ||
240 | { | ||
241 | if (!d->mKey.isNull()) { | ||
242 | return d->mKey.userID(0).comment(); | ||
243 | } | ||
244 | return QString(); | ||
245 | } | ||
246 | |||
247 | class SignaturePrivate | ||
248 | { | ||
249 | public: | ||
250 | Signature *q; | ||
251 | GpgME::Signature mSignature; | ||
252 | Key::Ptr mKey; | ||
253 | }; | ||
254 | |||
255 | Signature::Signature() | ||
256 | :d(std::unique_ptr<SignaturePrivate>(new SignaturePrivate)) | ||
257 | { | ||
258 | d->q = this; | ||
259 | } | ||
260 | |||
261 | |||
262 | Signature::Signature(SignaturePrivate *d_ptr) | ||
263 | :d(std::unique_ptr<SignaturePrivate>(d_ptr)) | ||
264 | { | ||
265 | d->q = this; | ||
266 | } | ||
267 | |||
268 | Signature::~Signature() | ||
269 | { | ||
270 | |||
271 | } | ||
272 | |||
273 | QDateTime Signature::creationDateTime() const | ||
274 | { | ||
275 | QDateTime dt; | ||
276 | dt.setTime_t(d->mSignature.creationTime()); | ||
277 | return dt; | ||
278 | } | ||
279 | |||
280 | QDateTime Signature::expirationDateTime() const | ||
281 | { | ||
282 | QDateTime dt; | ||
283 | dt.setTime_t(d->mSignature.expirationTime()); | ||
284 | return dt; | ||
285 | } | ||
286 | |||
287 | bool Signature::neverExpires() const | ||
288 | { | ||
289 | return d->mSignature.neverExpires(); | ||
290 | } | ||
291 | |||
292 | Key::Ptr Signature::key() const | ||
293 | { | ||
294 | return d->mKey; | ||
295 | } | ||
296 | |||
297 | class EncryptionPrivate | ||
298 | { | ||
299 | public: | ||
300 | Encryption *q; | ||
301 | std::vector<Key::Ptr> mRecipients; | ||
302 | Encryption::ErrorType mErrorType; | ||
303 | QString mErrorString; | ||
304 | }; | ||
305 | |||
306 | Encryption::Encryption(EncryptionPrivate *d_ptr) | ||
307 | :d(std::unique_ptr<EncryptionPrivate>(d_ptr)) | ||
308 | { | ||
309 | d->q = this; | ||
310 | } | ||
311 | |||
312 | Encryption::Encryption() | ||
313 | :d(std::unique_ptr<EncryptionPrivate>(new EncryptionPrivate)) | ||
314 | { | ||
315 | d->q = this; | ||
316 | d->mErrorType = Encryption::NoError; | ||
317 | } | ||
318 | |||
319 | Encryption::~Encryption() | ||
320 | { | ||
321 | |||
322 | } | ||
323 | |||
324 | std::vector<Key::Ptr> Encryption::recipients() const | ||
325 | { | ||
326 | return d->mRecipients; | ||
327 | } | ||
328 | |||
329 | QString Encryption::errorString() | ||
330 | { | ||
331 | return d->mErrorString; | ||
332 | } | ||
333 | |||
334 | Encryption::ErrorType Encryption::errorType() | ||
335 | { | ||
336 | return d->mErrorType; | ||
337 | } | ||
338 | |||
339 | |||
340 | class PartPrivate | ||
341 | { | ||
342 | public: | ||
343 | PartPrivate(Part *part); | ||
344 | void appendSubPart(Part::Ptr subpart); | ||
345 | |||
346 | QVector<Part::Ptr> subParts(); | ||
347 | |||
348 | Part *parent() const; | ||
349 | |||
350 | const MailMime::Ptr &mailMime() const; | ||
351 | void createMailMime(const MimeTreeParser::MimeMessagePart::Ptr &part); | ||
352 | void createMailMime(const MimeTreeParser::TextMessagePart::Ptr &part); | ||
353 | void createMailMime(const MimeTreeParser::AlternativeMessagePart::Ptr &part); | ||
354 | void createMailMime(const MimeTreeParser::HtmlMessagePart::Ptr &part); | ||
355 | void createMailMime(const MimeTreeParser::EncryptedMessagePart::Ptr &part); | ||
356 | |||
357 | static Encryption::Ptr createEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& part); | ||
358 | void appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr &part); | ||
359 | static QVector<Signature::Ptr> createSignature(const MimeTreeParser::SignedMessagePart::Ptr& part); | ||
360 | void appendSignature(const MimeTreeParser::SignedMessagePart::Ptr &part); | ||
361 | |||
362 | void setSignatures(const QVector<Signature::Ptr> &sigs); | ||
363 | void setEncryptions(const QVector<Encryption::Ptr> &encs); | ||
364 | |||
365 | const QVector<Encryption::Ptr> &encryptions() const; | ||
366 | const QVector<Signature::Ptr> &signatures() const; | ||
367 | private: | ||
368 | Part *q; | ||
369 | Part *mParent; | ||
370 | QVector<Part::Ptr> mSubParts; | ||
371 | QVector<Encryption::Ptr> mEncryptions; | ||
372 | QVector<Signature::Ptr> mSignatures; | ||
373 | MailMime::Ptr mMailMime; | ||
374 | }; | ||
375 | |||
376 | PartPrivate::PartPrivate(Part* part) | ||
377 | : q(part) | ||
378 | , mParent(Q_NULLPTR) | ||
379 | { | ||
380 | |||
381 | } | ||
382 | |||
383 | void PartPrivate::createMailMime(const MimeTreeParser::HtmlMessagePart::Ptr& part) | ||
384 | { | ||
385 | mMailMime = MailMime::Ptr(new MailMime); | ||
386 | mMailMime->d->mNode = part->mNode; | ||
387 | } | ||
388 | |||
389 | void PartPrivate::createMailMime(const MimeTreeParser::AlternativeMessagePart::Ptr& part) | ||
390 | { | ||
391 | mMailMime = MailMime::Ptr(new MailMime); | ||
392 | mMailMime->d->mNode = part->mNode; | ||
393 | } | ||
394 | |||
395 | void PartPrivate::createMailMime(const MimeTreeParser::TextMessagePart::Ptr& part) | ||
396 | { | ||
397 | mMailMime = MailMime::Ptr(new MailMime); | ||
398 | mMailMime->d->mNode = part->mNode; | ||
399 | } | ||
400 | |||
401 | void PartPrivate::createMailMime(const MimeTreeParser::MimeMessagePart::Ptr& part) | ||
402 | { | ||
403 | mMailMime = MailMime::Ptr(new MailMime); | ||
404 | mMailMime->d->mNode = part->mNode; | ||
405 | } | ||
406 | |||
407 | void PartPrivate::createMailMime(const MimeTreeParser::EncryptedMessagePart::Ptr& part) | ||
408 | { | ||
409 | mMailMime = MailMime::Ptr(new MailMime); | ||
410 | mMailMime->d->mNode = part->mNode; | ||
411 | } | ||
412 | |||
413 | void PartPrivate::appendSubPart(Part::Ptr subpart) | ||
414 | { | ||
415 | subpart->d->mParent = q; | ||
416 | mSubParts.append(subpart); | ||
417 | } | ||
418 | |||
419 | Encryption::Ptr PartPrivate::createEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& part) | ||
420 | { | ||
421 | QGpgME::KeyListJob *job = part->mCryptoProto->keyListJob(false); // local, no sigs | ||
422 | if (!job) { | ||
423 | qWarning() << "The Crypto backend does not support listing keys. "; | ||
424 | return Encryption::Ptr(); | ||
425 | } | ||
426 | |||
427 | auto encpriv = new EncryptionPrivate(); | ||
428 | if (part->passphraseError()) { | ||
429 | encpriv->mErrorType = Encryption::PassphraseError; | ||
430 | encpriv->mErrorString = part->mMetaData.errorText; | ||
431 | } else if (part->isEncrypted() && !part->isDecryptable()) { | ||
432 | encpriv->mErrorType = Encryption::KeyMissing; | ||
433 | encpriv->mErrorString = part->mMetaData.errorText; | ||
434 | } else if (!part->isEncrypted() && !part->isDecryptable()) { | ||
435 | encpriv->mErrorType = Encryption::UnknownError; | ||
436 | encpriv->mErrorString = part->mMetaData.errorText; | ||
437 | } else { | ||
438 | encpriv->mErrorType = Encryption::NoError; | ||
439 | } | ||
440 | |||
441 | foreach(const auto &recipient, part->mDecryptRecipients) { | ||
442 | std::vector<GpgME::Key> found_keys; | ||
443 | const auto &keyid = recipient.keyID(); | ||
444 | GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(keyid)), false, found_keys); | ||
445 | if (res.error()) { | ||
446 | qWarning() << "Error while searching key for Fingerprint: " << keyid; | ||
447 | continue; | ||
448 | } | ||
449 | if (found_keys.size() > 1) { | ||
450 | // Should not Happen | ||
451 | qWarning() << "Oops: Found more then one Key for Fingerprint: " << keyid; | ||
452 | } | ||
453 | if (found_keys.size() != 1) { | ||
454 | // Should not Happen at this point | ||
455 | qWarning() << "Oops: Found no Key for Fingerprint: " << keyid; | ||
456 | auto keypriv = new KeyPrivate; | ||
457 | keypriv->mKeyID = keyid; | ||
458 | encpriv->mRecipients.push_back(Key::Ptr(new Key(keypriv))); | ||
459 | } else { | ||
460 | auto key = found_keys[0]; | ||
461 | auto keypriv = new KeyPrivate; | ||
462 | keypriv->mKey = key; | ||
463 | encpriv->mRecipients.push_back(Key::Ptr(new Key(keypriv))); | ||
464 | } | ||
465 | } | ||
466 | return Encryption::Ptr(new Encryption(encpriv)); | ||
467 | } | ||
468 | |||
469 | void PartPrivate::appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& part) | ||
470 | { | ||
471 | mEncryptions.append(createEncryption(part)); | ||
472 | } | ||
473 | |||
474 | void PartPrivate::setEncryptions(const QVector< Encryption::Ptr >& encs) | ||
475 | { | ||
476 | mEncryptions = encs; | ||
477 | } | ||
478 | |||
479 | QVector<Signature::Ptr> PartPrivate::createSignature(const MimeTreeParser::SignedMessagePart::Ptr& part) | ||
480 | { | ||
481 | QVector<Signature::Ptr> sigs; | ||
482 | QGpgME::KeyListJob *job = part->mCryptoProto->keyListJob(false); // local, no sigs | ||
483 | if (!job) { | ||
484 | qWarning() << "The Crypto backend does not support listing keys. "; | ||
485 | return sigs; | ||
486 | } | ||
487 | |||
488 | foreach(const auto &sig, part->mSignatures) { | ||
489 | auto sigpriv = new SignaturePrivate(); | ||
490 | sigpriv->mSignature = sig; | ||
491 | auto signature = std::make_shared<Signature>(sigpriv); | ||
492 | sigs.append(signature); | ||
493 | |||
494 | std::vector<GpgME::Key> found_keys; | ||
495 | const auto &keyid = sig.fingerprint(); | ||
496 | GpgME::KeyListResult res = job->exec(QStringList(QLatin1String(keyid)), false, found_keys); | ||
497 | if (res.error()) { | ||
498 | qWarning() << "Error while searching key for Fingerprint: " << keyid; | ||
499 | continue; | ||
500 | } | ||
501 | if (found_keys.size() > 1) { | ||
502 | // Should not Happen | ||
503 | qWarning() << "Oops: Found more then one Key for Fingerprint: " << keyid; | ||
504 | continue; | ||
505 | } | ||
506 | if (found_keys.size() != 1) { | ||
507 | // Should not Happen at this point | ||
508 | qWarning() << "Oops: Found no Key for Fingerprint: " << keyid; | ||
509 | continue; | ||
510 | } else { | ||
511 | auto key = found_keys[0]; | ||
512 | auto keypriv = new KeyPrivate; | ||
513 | keypriv->mKey = key; | ||
514 | sigpriv->mKey = Key::Ptr(new Key(keypriv)); | ||
515 | } | ||
516 | } | ||
517 | return sigs; | ||
518 | } | ||
519 | |||
520 | void PartPrivate::appendSignature(const MimeTreeParser::SignedMessagePart::Ptr& part) | ||
521 | { | ||
522 | mSignatures.append(createSignature(part)); | ||
523 | } | ||
524 | |||
525 | |||
526 | void PartPrivate::setSignatures(const QVector< Signature::Ptr >& sigs) | ||
527 | { | ||
528 | mSignatures = sigs; | ||
529 | } | ||
530 | |||
531 | Part *PartPrivate::parent() const | ||
532 | { | ||
533 | return mParent; | ||
534 | } | ||
535 | |||
536 | QVector< Part::Ptr > PartPrivate::subParts() | ||
537 | { | ||
538 | return mSubParts; | ||
539 | } | ||
540 | |||
541 | const MailMime::Ptr& PartPrivate::mailMime() const | ||
542 | { | ||
543 | return mMailMime; | ||
544 | } | ||
545 | |||
546 | const QVector< Encryption::Ptr >& PartPrivate::encryptions() const | ||
547 | { | ||
548 | return mEncryptions; | ||
549 | } | ||
550 | |||
551 | const QVector< Signature::Ptr >& PartPrivate::signatures() const | ||
552 | { | ||
553 | return mSignatures; | ||
554 | } | ||
555 | |||
556 | Part::Part() | ||
557 | : d(std::unique_ptr<PartPrivate>(new PartPrivate(this))) | ||
558 | { | ||
559 | |||
560 | } | ||
561 | |||
562 | bool Part::hasSubParts() const | ||
563 | { | ||
564 | return !subParts().isEmpty(); | ||
565 | } | ||
566 | |||
567 | QVector<Part::Ptr> Part::subParts() const | ||
568 | { | ||
569 | return d->subParts(); | ||
570 | } | ||
571 | |||
572 | QByteArray Part::type() const | ||
573 | { | ||
574 | return "Part"; | ||
575 | } | ||
576 | |||
577 | QVector<QByteArray> Part::availableContents() const | ||
578 | { | ||
579 | return QVector<QByteArray>(); | ||
580 | } | ||
581 | |||
582 | QVector<Content::Ptr> Part::content() const | ||
583 | { | ||
584 | return content(availableContents().first()); | ||
585 | } | ||
586 | |||
587 | QVector<Content::Ptr> Part::content(const QByteArray& ct) const | ||
588 | { | ||
589 | return QVector<Content::Ptr>(); | ||
590 | } | ||
591 | |||
592 | QVector<Encryption::Ptr> Part::encryptions() const | ||
593 | { | ||
594 | auto ret = d->encryptions(); | ||
595 | auto parent = d->parent(); | ||
596 | if (parent) { | ||
597 | ret.append(parent->encryptions()); | ||
598 | } | ||
599 | return ret; | ||
600 | } | ||
601 | |||
602 | QVector<Signature::Ptr> Part::signatures() const | ||
603 | { | ||
604 | auto ret = d->signatures(); | ||
605 | auto parent = d->parent(); | ||
606 | if (parent) { | ||
607 | ret.append(parent->signatures()); | ||
608 | } | ||
609 | return ret; | ||
610 | } | ||
611 | |||
612 | MailMime::Ptr Part::mailMime() const | ||
613 | { | ||
614 | return d->mailMime(); | ||
615 | } | ||
616 | |||
617 | Part *Part::parent() const | ||
618 | { | ||
619 | return d->parent(); | ||
620 | } | ||
621 | |||
622 | class ContentPrivate | ||
623 | { | ||
624 | public: | ||
625 | QByteArray mContent; | ||
626 | QByteArray mCodec; | ||
627 | Part *mParent; | ||
628 | Content *q; | ||
629 | MailMime::Ptr mMailMime; | ||
630 | QVector<Encryption::Ptr> mEncryptions; | ||
631 | QVector<Signature::Ptr> mSignatures; | ||
632 | void appendSignature(const MimeTreeParser::SignedMessagePart::Ptr &sig); | ||
633 | void appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr &enc); | ||
634 | }; | ||
635 | |||
636 | void ContentPrivate::appendEncryption(const MimeTreeParser::EncryptedMessagePart::Ptr& enc) | ||
637 | { | ||
638 | mEncryptions.append(PartPrivate::createEncryption(enc)); | ||
639 | } | ||
640 | |||
641 | void ContentPrivate::appendSignature(const MimeTreeParser::SignedMessagePart::Ptr& sig) | ||
642 | { | ||
643 | mSignatures.append(PartPrivate::createSignature(sig)); | ||
644 | } | ||
645 | |||
646 | |||
647 | Content::Content(const QByteArray& content, Part *parent) | ||
648 | : d(std::unique_ptr<ContentPrivate>(new ContentPrivate)) | ||
649 | { | ||
650 | d->q = this; | ||
651 | d->mContent = content; | ||
652 | d->mCodec = "utf-8"; | ||
653 | d->mParent = parent; | ||
654 | } | ||
655 | |||
656 | Content::Content(ContentPrivate* d_ptr) | ||
657 | : d(std::unique_ptr<ContentPrivate>(d_ptr)) | ||
658 | { | ||
659 | d->q = this; | ||
660 | } | ||
661 | |||
662 | Content::~Content() | ||
663 | { | ||
664 | } | ||
665 | |||
666 | QVector<Encryption::Ptr> Content::encryptions() const | ||
667 | { | ||
668 | auto ret = d->mEncryptions; | ||
669 | if (d->mParent) { | ||
670 | ret.append(d->mParent->encryptions()); | ||
671 | } | ||
672 | return ret; | ||
673 | } | ||
674 | |||
675 | QVector<Signature::Ptr> Content::signatures() const | ||
676 | { | ||
677 | auto ret = d->mSignatures; | ||
678 | if (d->mParent) { | ||
679 | ret.append(d->mParent->signatures()); | ||
680 | } | ||
681 | return ret; | ||
682 | } | ||
683 | |||
684 | QByteArray Content::content() const | ||
685 | { | ||
686 | return d->mContent; | ||
687 | } | ||
688 | |||
689 | QByteArray Content::charset() const | ||
690 | { | ||
691 | return d->mCodec; | ||
692 | } | ||
693 | |||
694 | QString Content::encodedContent() const | ||
695 | { | ||
696 | return QString::fromUtf8(content()); | ||
697 | // TODO: should set "raw" content not the already utf8 encoded content | ||
698 | // return encodedContent(charset()); | ||
699 | } | ||
700 | |||
701 | QString Content::encodedContent(const QByteArray &charset) const | ||
702 | { | ||
703 | QTextCodec *codec = QTextCodec::codecForName(charset); | ||
704 | return codec->toUnicode(content()); | ||
705 | } | ||
706 | |||
707 | QByteArray Content::type() const | ||
708 | { | ||
709 | return "Content"; | ||
710 | } | ||
711 | |||
712 | MailMime::Ptr Content::mailMime() const | ||
713 | { | ||
714 | if (d->mMailMime) { | ||
715 | return d->mMailMime; | ||
716 | } else { | ||
717 | return d->mParent->mailMime(); | ||
718 | } | ||
719 | } | ||
720 | |||
721 | Part *Content::parent() const | ||
722 | { | ||
723 | return d->mParent; | ||
724 | } | ||
725 | |||
726 | HtmlContent::HtmlContent(const QByteArray& content, Part* parent) | ||
727 | : Content(content, parent) | ||
728 | { | ||
729 | |||
730 | } | ||
731 | |||
732 | QByteArray HtmlContent::type() const | ||
733 | { | ||
734 | return "HtmlContent"; | ||
735 | } | ||
736 | |||
737 | PlainTextContent::PlainTextContent(const QByteArray& content, Part* parent) | ||
738 | : Content(content, parent) | ||
739 | { | ||
740 | |||
741 | } | ||
742 | |||
743 | PlainTextContent::PlainTextContent(ContentPrivate* d_ptr) | ||
744 | : Content(d_ptr) | ||
745 | { | ||
746 | |||
747 | } | ||
748 | |||
749 | HtmlContent::HtmlContent(ContentPrivate* d_ptr) | ||
750 | : Content(d_ptr) | ||
751 | { | ||
752 | |||
753 | } | ||
754 | |||
755 | |||
756 | QByteArray PlainTextContent::type() const | ||
757 | { | ||
758 | return "PlainTextContent"; | ||
759 | } | ||
760 | |||
761 | class AlternativePartPrivate | ||
762 | { | ||
763 | public: | ||
764 | void fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part); | ||
765 | |||
766 | QVector<Content::Ptr> content(const QByteArray &ct) const; | ||
767 | |||
768 | AlternativePart *q; | ||
769 | |||
770 | QVector<QByteArray> types() const; | ||
771 | |||
772 | private: | ||
773 | QMap<QByteArray, QVector<Content::Ptr>> mContent; | ||
774 | QVector<QByteArray> mTypes; | ||
775 | }; | ||
776 | |||
777 | void AlternativePartPrivate::fillFrom(MimeTreeParser::AlternativeMessagePart::Ptr part) | ||
778 | { | ||
779 | mTypes = QVector<QByteArray>() << "html" << "plaintext"; | ||
780 | |||
781 | Content::Ptr content = std::make_shared<HtmlContent>(part->htmlContent().toLocal8Bit(), q); | ||
782 | mContent["html"].append(content); | ||
783 | content = std::make_shared<PlainTextContent>(part->plaintextContent().toLocal8Bit(), q); | ||
784 | mContent["plaintext"].append(content); | ||
785 | q->reachParentD()->createMailMime(part); | ||
786 | } | ||
787 | |||
788 | QVector<QByteArray> AlternativePartPrivate::types() const | ||
789 | { | ||
790 | return mTypes; | ||
791 | } | ||
792 | |||
793 | QVector<Content::Ptr> AlternativePartPrivate::content(const QByteArray& ct) const | ||
794 | { | ||
795 | return mContent[ct]; | ||
796 | } | ||
797 | |||
798 | AlternativePart::AlternativePart() | ||
799 | : d(std::unique_ptr<AlternativePartPrivate>(new AlternativePartPrivate)) | ||
800 | { | ||
801 | d->q = this; | ||
802 | } | ||
803 | |||
804 | AlternativePart::~AlternativePart() | ||
805 | { | ||
806 | |||
807 | } | ||
808 | |||
809 | QByteArray AlternativePart::type() const | ||
810 | { | ||
811 | return "AlternativePart"; | ||
812 | } | ||
813 | |||
814 | QVector<QByteArray> AlternativePart::availableContents() const | ||
815 | { | ||
816 | return d->types(); | ||
817 | } | ||
818 | |||
819 | QVector<Content::Ptr> AlternativePart::content(const QByteArray& ct) const | ||
820 | { | ||
821 | return d->content(ct); | ||
822 | } | ||
823 | |||
824 | PartPrivate* AlternativePart::reachParentD() const | ||
825 | { | ||
826 | return Part::d.get(); | ||
827 | } | ||
828 | |||
829 | class SinglePartPrivate | ||
830 | { | ||
831 | public: | ||
832 | void fillFrom(const MimeTreeParser::TextMessagePart::Ptr &part); | ||
833 | void fillFrom(const MimeTreeParser::HtmlMessagePart::Ptr &part); | ||
834 | void fillFrom(const MimeTreeParser::AttachmentMessagePart::Ptr &part); | ||
835 | void createEncryptionFailBlock(const MimeTreeParser::EncryptedMessagePart::Ptr &part); | ||
836 | SinglePart *q; | ||
837 | |||
838 | QVector<Content::Ptr> mContent; | ||
839 | QByteArray mType; | ||
840 | }; | ||
841 | |||
842 | void SinglePartPrivate::fillFrom(const MimeTreeParser::TextMessagePart::Ptr &part) | ||
843 | { | ||
844 | mType = "plaintext"; | ||
845 | mContent.clear(); | ||
846 | foreach (const auto &mp, part->subParts()) { | ||
847 | auto d_ptr = new ContentPrivate; | ||
848 | d_ptr->mContent = mp->text().toUtf8(); // TODO: should set "raw" content not the already utf8 encoded content | ||
849 | d_ptr->mParent = q; | ||
850 | const auto enc = mp.dynamicCast<MimeTreeParser::EncryptedMessagePart>(); | ||
851 | auto sig = mp.dynamicCast<MimeTreeParser::SignedMessagePart>(); | ||
852 | if (enc) { | ||
853 | d_ptr->appendEncryption(enc); | ||
854 | if (!enc->isDecryptable()) { | ||
855 | d_ptr->mContent = QByteArray(); | ||
856 | } | ||
857 | const auto s = enc->subParts(); | ||
858 | if (s.size() == 1) { | ||
859 | sig = s[0].dynamicCast<MimeTreeParser::SignedMessagePart>(); | ||
860 | } | ||
861 | } | ||
862 | if (sig) { | ||
863 | d_ptr->appendSignature(sig); | ||
864 | } | ||
865 | mContent.append(std::make_shared<PlainTextContent>(d_ptr)); | ||
866 | q->reachParentD()->createMailMime(part); | ||
867 | d_ptr->mCodec = q->mailMime()->charset(); | ||
868 | } | ||
869 | } | ||
870 | |||
871 | void SinglePartPrivate::fillFrom(const MimeTreeParser::HtmlMessagePart::Ptr &part) | ||
872 | { | ||
873 | mType = "html"; | ||
874 | mContent.clear(); | ||
875 | mContent.append(std::make_shared<HtmlContent>(part->text().toUtf8(), q)); | ||
876 | q->reachParentD()->createMailMime(part); | ||
877 | } | ||
878 | |||
879 | void SinglePartPrivate::fillFrom(const MimeTreeParser::AttachmentMessagePart::Ptr &part) | ||
880 | { | ||
881 | QMimeDatabase mimeDb; | ||
882 | q->reachParentD()->createMailMime(part.staticCast<MimeTreeParser::TextMessagePart>()); | ||
883 | const auto mimetype = q->mailMime()->mimetype(); | ||
884 | const auto content = q->mailMime()->decodedContent(); | ||
885 | mContent.clear(); | ||
886 | if (mimetype == mimeDb.mimeTypeForName("text/plain")) { | ||
887 | mType = "plaintext"; | ||
888 | mContent.append(std::make_shared<PlainTextContent>(content, q)); | ||
889 | } else if (mimetype == mimeDb.mimeTypeForName("text/html")) { | ||
890 | mType = "html"; | ||
891 | mContent.append(std::make_shared<HtmlContent>(content, q)); | ||
892 | } else { | ||
893 | mType = mimetype.name().toUtf8(); | ||
894 | mContent.append(std::make_shared<Content>(content, q)); | ||
895 | } | ||
896 | } | ||
897 | |||
898 | void SinglePartPrivate::createEncryptionFailBlock(const MimeTreeParser::EncryptedMessagePart::Ptr &part) | ||
899 | { | ||
900 | mType = "plaintext"; | ||
901 | mContent.clear(); | ||
902 | mContent.append(std::make_shared<PlainTextContent>(QByteArray(), q)); | ||
903 | q->reachParentD()->createMailMime(part); | ||
904 | } | ||
905 | |||
906 | SinglePart::SinglePart() | ||
907 | : d(std::unique_ptr<SinglePartPrivate>(new SinglePartPrivate)) | ||
908 | { | ||
909 | d->q = this; | ||
910 | } | ||
911 | |||
912 | SinglePart::~SinglePart() | ||
913 | { | ||
914 | |||
915 | } | ||
916 | |||
917 | QVector<QByteArray> SinglePart::availableContents() const | ||
918 | { | ||
919 | return QVector<QByteArray>() << d->mType; | ||
920 | } | ||
921 | |||
922 | QVector< Content::Ptr > SinglePart::content(const QByteArray &ct) const | ||
923 | { | ||
924 | if (ct == d->mType) { | ||
925 | return d->mContent; | ||
926 | } | ||
927 | return QVector<Content::Ptr>(); | ||
928 | } | ||
929 | |||
930 | QByteArray SinglePart::type() const | ||
931 | { | ||
932 | return "SinglePart"; | ||
933 | } | ||
934 | |||
935 | PartPrivate* SinglePart::reachParentD() const | ||
936 | { | ||
937 | return Part::d.get(); | ||
938 | } | ||
939 | |||
940 | ParserPrivate::ParserPrivate(Parser* parser) | ||
941 | : q(parser) | ||
942 | , mNodeHelper(std::make_shared<MimeTreeParser::NodeHelper>()) | ||
943 | { | ||
944 | |||
945 | } | ||
946 | |||
947 | void ParserPrivate::setMessage(const QByteArray& mimeMessage) | ||
948 | { | ||
949 | const auto mailData = KMime::CRLFtoLF(mimeMessage); | ||
950 | mMsg = KMime::Message::Ptr(new KMime::Message); | ||
951 | mMsg->setContent(mailData); | ||
952 | mMsg->parse(); | ||
953 | |||
954 | // render the mail | ||
955 | StringHtmlWriter htmlWriter; | ||
956 | ObjectTreeSource source(&htmlWriter); | ||
957 | MimeTreeParser::ObjectTreeParser otp(&source, mNodeHelper.get()); | ||
958 | |||
959 | otp.parseObjectTree(mMsg.data()); | ||
960 | mPartTree = otp.parsedPart().dynamicCast<MimeTreeParser::MessagePart>(); | ||
961 | |||
962 | mEmbeddedPartMap = htmlWriter.embeddedParts(); | ||
963 | mHtml = htmlWriter.html(); | ||
964 | |||
965 | mTree = std::make_shared<Part>(); | ||
966 | createTree(mPartTree, mTree); | ||
967 | } | ||
968 | |||
969 | |||
970 | void ParserPrivate::createTree(const MimeTreeParser::MessagePart::Ptr &start, const Part::Ptr &tree) | ||
971 | { | ||
972 | foreach (const auto &mp, start->subParts()) { | ||
973 | const auto m = mp.dynamicCast<MimeTreeParser::MessagePart>(); | ||
974 | const auto text = mp.dynamicCast<MimeTreeParser::TextMessagePart>(); | ||
975 | const auto alternative = mp.dynamicCast<MimeTreeParser::AlternativeMessagePart>(); | ||
976 | const auto html = mp.dynamicCast<MimeTreeParser::HtmlMessagePart>(); | ||
977 | const auto attachment = mp.dynamicCast<MimeTreeParser::AttachmentMessagePart>(); | ||
978 | if (attachment) { | ||
979 | auto part = std::make_shared<SinglePart>(); | ||
980 | part->d->fillFrom(attachment); | ||
981 | tree->d->appendSubPart(part); | ||
982 | } else if (text) { | ||
983 | auto part = std::make_shared<SinglePart>(); | ||
984 | part->d->fillFrom(text); | ||
985 | tree->d->appendSubPart(part); | ||
986 | } else if (alternative) { | ||
987 | auto part = std::make_shared<AlternativePart>(); | ||
988 | part->d->fillFrom(alternative); | ||
989 | tree->d->appendSubPart(part); | ||
990 | } else if (html) { | ||
991 | auto part = std::make_shared<SinglePart>(); | ||
992 | part->d->fillFrom(html); | ||
993 | tree->d->appendSubPart(part); | ||
994 | } else { | ||
995 | const auto enc = mp.dynamicCast<MimeTreeParser::EncryptedMessagePart>(); | ||
996 | const auto sig = mp.dynamicCast<MimeTreeParser::SignedMessagePart>(); | ||
997 | if (enc || sig) { | ||
998 | auto subTree = std::make_shared<Part>(); | ||
999 | if (enc) { | ||
1000 | subTree->d->appendEncryption(enc); | ||
1001 | if (!enc->isDecryptable()) { | ||
1002 | auto part = std::make_shared<SinglePart>(); | ||
1003 | part->d->createEncryptionFailBlock(enc); | ||
1004 | part->reachParentD()->setEncryptions(subTree->d->encryptions()); | ||
1005 | tree->d->appendSubPart(part); | ||
1006 | return; | ||
1007 | } | ||
1008 | } | ||
1009 | if (sig) { | ||
1010 | subTree->d->appendSignature(sig); | ||
1011 | } | ||
1012 | createTree(m, subTree); | ||
1013 | foreach(const auto &p, subTree->subParts()) { | ||
1014 | tree->d->appendSubPart(p); | ||
1015 | if (enc) { | ||
1016 | p->d->setEncryptions(subTree->d->encryptions()); | ||
1017 | } | ||
1018 | if (sig) { | ||
1019 | p->d->setSignatures(subTree->d->signatures()); | ||
1020 | } | ||
1021 | } | ||
1022 | } else { | ||
1023 | createTree(m, tree); | ||
1024 | } | ||
1025 | } | ||
1026 | } | ||
1027 | } | ||
1028 | |||
1029 | Parser::Parser(const QByteArray& mimeMessage) | ||
1030 | :d(std::unique_ptr<ParserPrivate>(new ParserPrivate(this))) | ||
1031 | { | ||
1032 | d->setMessage(mimeMessage); | ||
1033 | } | ||
1034 | |||
1035 | Parser::~Parser() | ||
1036 | { | ||
1037 | } | ||
1038 | |||
1039 | Part::Ptr Parser::getPart(const QUrl &url) | ||
1040 | { | ||
1041 | if (url.scheme() == QStringLiteral("cid") && !url.path().isEmpty()) { | ||
1042 | const auto cid = url.path(); | ||
1043 | return find(d->mTree, [&cid](const Part::Ptr &p){ | ||
1044 | const auto mime = p->mailMime(); | ||
1045 | return mime->cid() == cid; | ||
1046 | }); | ||
1047 | } | ||
1048 | return Part::Ptr(); | ||
1049 | } | ||
1050 | |||
1051 | QVector<Part::Ptr> Parser::collectContentParts() const | ||
1052 | { | ||
1053 | return collect(d->mTree, [](const Part::Ptr &p){return p->type() != "EncapsulatedPart";}, | ||
1054 | [](const Content::Ptr &content){ | ||
1055 | const auto mime = content->mailMime(); | ||
1056 | |||
1057 | if (!mime) { | ||
1058 | return true; | ||
1059 | } | ||
1060 | |||
1061 | if (mime->isFirstTextPart()) { | ||
1062 | return true; | ||
1063 | } | ||
1064 | |||
1065 | { | ||
1066 | auto _mime = content->parent()->mailMime(); | ||
1067 | while (_mime) { | ||
1068 | if (_mime && (_mime->isTopLevelPart() || _mime->isFirstTextPart())) { | ||
1069 | return true; | ||
1070 | } | ||
1071 | if (_mime->isFirstPart()) { | ||
1072 | _mime = _mime->parent(); | ||
1073 | } else { | ||
1074 | break; | ||
1075 | } | ||
1076 | } | ||
1077 | } | ||
1078 | |||
1079 | const auto ctname = mime->mimetype().name().trimmed().toLower(); | ||
1080 | bool mightContent = (content->type() != "Content"); //Content we understand | ||
1081 | |||
1082 | const auto cd = mime->disposition(); | ||
1083 | if (cd && cd == MailMime::Inline) { | ||
1084 | return mightContent; | ||
1085 | } | ||
1086 | |||
1087 | if (cd && cd == MailMime::Attachment) { | ||
1088 | return false; | ||
1089 | } | ||
1090 | |||
1091 | if ((ctname.startsWith("text/") || ctname.isEmpty()) && | ||
1092 | (!mime || mime->filename().trimmed().isEmpty())) { | ||
1093 | // text/* w/o filename parameter: | ||
1094 | return true; | ||
1095 | } | ||
1096 | return false; | ||
1097 | }); | ||
1098 | } | ||
1099 | |||
1100 | |||
1101 | QVector<Part::Ptr> Parser::collectAttachmentParts() const | ||
1102 | { | ||
1103 | return collect(d->mTree, [](const Part::Ptr &p){return p->type() != "EncapsulatedPart";}, | ||
1104 | [](const Content::Ptr &content){ | ||
1105 | const auto mime = content->mailMime(); | ||
1106 | |||
1107 | if (!mime) { | ||
1108 | return false; | ||
1109 | } | ||
1110 | |||
1111 | if (mime->isFirstTextPart()) { | ||
1112 | return false; | ||
1113 | } | ||
1114 | |||
1115 | { | ||
1116 | QMimeDatabase mimeDb; | ||
1117 | auto _mime = content->parent()->mailMime(); | ||
1118 | const auto parent = _mime->parent(); | ||
1119 | if (parent) { | ||
1120 | const auto mimetype = parent->mimetype(); | ||
1121 | if (mimetype == mimeDb.mimeTypeForName("multipart/related")) { | ||
1122 | return false; | ||
1123 | } | ||
1124 | } | ||
1125 | while (_mime) { | ||
1126 | if (_mime && (_mime->isTopLevelPart() || _mime->isFirstTextPart())) { | ||
1127 | return false; | ||
1128 | } | ||
1129 | if (_mime->isFirstPart()) { | ||
1130 | _mime = _mime->parent(); | ||
1131 | } else { | ||
1132 | break; | ||
1133 | } | ||
1134 | } | ||
1135 | } | ||
1136 | |||
1137 | const auto ctname = mime->mimetype().name().trimmed().toLower(); | ||
1138 | bool mightContent = (content->type() != "Content"); //Content we understand | ||
1139 | |||
1140 | const auto cd = mime->disposition(); | ||
1141 | if (cd && cd == MailMime::Inline) { | ||
1142 | // explict "inline" disposition: | ||
1143 | return !mightContent; | ||
1144 | } | ||
1145 | if (cd && cd == MailMime::Attachment) { | ||
1146 | // explicit "attachment" disposition: | ||
1147 | return true; | ||
1148 | } | ||
1149 | |||
1150 | const auto ct = mime->mimetype(); | ||
1151 | if ((ctname.startsWith("text/") || ctname.isEmpty()) && | ||
1152 | (!mime || mime->filename().trimmed().isEmpty())) { | ||
1153 | // text/* w/o filename parameter: | ||
1154 | return false; | ||
1155 | } | ||
1156 | return true; | ||
1157 | }); | ||
1158 | } | ||
1159 | |||
1160 | QVector<Part::Ptr> Parser::collect(const Part::Ptr &start, std::function<bool(const Part::Ptr &)> select, std::function<bool(const Content::Ptr &)> filter) const | ||
1161 | { | ||
1162 | QVector<Part::Ptr> ret; | ||
1163 | foreach (const auto &part, start->subParts()) { | ||
1164 | QVector<QByteArray> contents; | ||
1165 | foreach(const auto &ct, part->availableContents()) { | ||
1166 | foreach(const auto &content, part->content(ct)) { | ||
1167 | if (filter(content)) { | ||
1168 | contents.append(ct); | ||
1169 | break; | ||
1170 | } | ||
1171 | } | ||
1172 | } | ||
1173 | if (!contents.isEmpty()) { | ||
1174 | ret.append(part); | ||
1175 | } | ||
1176 | if (select(part)){ | ||
1177 | ret += collect(part, select, filter); | ||
1178 | } | ||
1179 | } | ||
1180 | return ret; | ||
1181 | } | ||
1182 | |||
1183 | Part::Ptr Parser::find(const Part::Ptr &start, std::function<bool(const Part::Ptr &)> select) const | ||
1184 | { | ||
1185 | foreach (const auto &part, start->subParts()) { | ||
1186 | if (select(part)) { | ||
1187 | return part; | ||
1188 | } | ||
1189 | const auto ret = find(part, select); | ||
1190 | if (ret) { | ||
1191 | return ret; | ||
1192 | } | ||
1193 | } | ||
1194 | return Part::Ptr(); | ||
1195 | } | ||