1 /* -*- c++ -*-
2 kmime_headers.cpp
3
4 KMime, the KDE Internet mail/usenet news message library.
5 SPDX-FileCopyrightText: 2001-2002 the KMime authors.
6 See file AUTHORS for details
7 SPDX-FileCopyrightText: 2006 Volker Krause <vkrause@kde.org>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10 */
11 /**
12 @file
13 This file is part of the API for handling @ref MIME data and
14 defines the various header classes:
15 - header's base class defining the common interface
16 - generic base classes for different types of fields
17 - incompatible, Structured-based field classes
18 - compatible, Unstructured-based field classes
19
20 @brief
21 Defines the various headers classes.
22
23 @authors the KMime authors (see AUTHORS file),
24 Volker Krause \<vkrause@kde.org\>
25 */
26
27 #include "kmime_headers.h"
28 #include "kmime_headers_p.h"
29
30 #include "kmime_util.h"
31 #include "kmime_util_p.h"
32 #include "kmime_codecs.h"
33 #include "kmime_content.h"
34 #include "kmime_headerfactory_p.h"
35 #include "kmime_debug.h"
36 #include "kmime_warning.h"
37
38 #include <KCharsets>
39 #include <KCodecs>
40
41
42 #include <assert.h>
43 #include <ctype.h>
44
45 // macro to generate a default constructor implementation
46 #define kmime_mk_trivial_ctor( subclass, baseclass ) \
47 subclass::subclass() : baseclass() \
48 { \
49 } \
50 \
51 subclass::~subclass() {}
52
53 // end kmime_mk_trivial_ctor
54
55 #define kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \
56 subclass::subclass() : baseclass( new subclass##Private ) \
57 { \
58 } \
59 \
60 subclass::~subclass() { \
61 Q_D(subclass); \
62 delete d; /* see comment above the BasePrivate class */ \
63 d_ptr = nullptr; \
64 }
65
66 // end kmime_mk_trivial_ctor_with_dptr
67
68 #define kmime_mk_trivial_ctor_with_name( subclass, baseclass, name ) \
69 kmime_mk_trivial_ctor( subclass, baseclass ) \
70 \
71 const char *subclass::type() const \
72 { \
73 return staticType(); \
74 } \
75 const char *subclass::staticType() { return #name; }
76
77 #define kmime_mk_trivial_ctor_with_name_and_dptr( subclass, baseclass, name ) \
78 kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \
79 const char *subclass::type() const { return staticType(); } \
80 const char *subclass::staticType() { return #name; }
81
82 #define kmime_mk_dptr_ctor( subclass, baseclass ) \
83 subclass::subclass( subclass##Private *d ) : baseclass( d ) {}
84
85 using namespace KMime;
86 using namespace KMime::Headers;
87 using namespace KMime::Types;
88 using namespace KMime::HeaderParsing;
89
90 namespace KMime
91 {
92 namespace Headers
93 {
94 //-----<Base>----------------------------------
Base()95 Base::Base() : d_ptr(new BasePrivate)
96 {
97 }
98
Base(BasePrivate * dd)99 Base::Base(BasePrivate *dd) :
100 d_ptr(dd)
101 {
102 }
103
~Base()104 Base::~Base()
105 {
106 delete d_ptr;
107 d_ptr = nullptr;
108 }
109
from7BitString(const char * s,size_t len)110 void Base::from7BitString(const char *s, size_t len)
111 {
112 from7BitString(QByteArray::fromRawData(s, len));
113 }
114
rfc2047Charset() const115 QByteArray Base::rfc2047Charset() const
116 {
117 if (d_ptr->encCS.isEmpty()) {
118 return Content::defaultCharset();
119 } else {
120 return d_ptr->encCS;
121 }
122 }
123
setRFC2047Charset(const QByteArray & cs)124 void Base::setRFC2047Charset(const QByteArray &cs)
125 {
126 d_ptr->encCS = cachedCharset(cs);
127 }
128
type() const129 const char *Base::type() const
130 {
131 return "";
132 }
133
is(const char * t) const134 bool Base::is(const char *t) const
135 {
136 return qstricmp(t, type()) == 0;
137 }
138
isMimeHeader() const139 bool Base::isMimeHeader() const
140 {
141 return qstrnicmp(type(), "Content-", 8) == 0;
142 }
143
typeIntro() const144 QByteArray Base::typeIntro() const
145 {
146 return QByteArray(type()) + ": ";
147 }
148
149 //-----</Base>---------------------------------
150
151 namespace Generics
152 {
153
154 //-----<Unstructured>-------------------------
155
156 //@cond PRIVATE
kmime_mk_dptr_ctor(Unstructured,Base)157 kmime_mk_dptr_ctor(Unstructured, Base)
158 //@endcond
159
160 Unstructured::Unstructured() : Base(new UnstructuredPrivate)
161 {
162 }
163
~Unstructured()164 Unstructured::~Unstructured()
165 {
166 Q_D(Unstructured);
167 delete d;
168 d_ptr = nullptr;
169 }
170
from7BitString(const QByteArray & s)171 void Unstructured::from7BitString(const QByteArray &s)
172 {
173 Q_D(Unstructured);
174 d->decoded = KCodecs::decodeRFC2047String(s, &d->encCS, Content::defaultCharset());
175 }
176
as7BitString(bool withHeaderType) const177 QByteArray Unstructured::as7BitString(bool withHeaderType) const
178 {
179 const Q_D(Unstructured);
180 QByteArray result;
181 if (withHeaderType) {
182 result = typeIntro();
183 }
184 result += encodeRFC2047String(d->decoded, d->encCS) ;
185
186 return result;
187 }
188
fromUnicodeString(const QString & s,const QByteArray & b)189 void Unstructured::fromUnicodeString(const QString &s, const QByteArray &b)
190 {
191 Q_D(Unstructured);
192 d->decoded = s;
193 d->encCS = cachedCharset(b);
194 }
195
asUnicodeString() const196 QString Unstructured::asUnicodeString() const
197 {
198 return d_func()->decoded;
199 }
200
clear()201 void Unstructured::clear()
202 {
203 Q_D(Unstructured);
204 d->decoded.truncate(0);
205 }
206
isEmpty() const207 bool Unstructured::isEmpty() const
208 {
209 return d_func()->decoded.isEmpty();
210 }
211
212 //-----</Unstructured>-------------------------
213
214 //-----<Structured>-------------------------
215
Structured()216 Structured::Structured() : Base(new StructuredPrivate)
217 {
218 }
219
kmime_mk_dptr_ctor(Structured,Base)220 kmime_mk_dptr_ctor(Structured, Base)
221
222 Structured::~Structured()
223 {
224 Q_D(Structured);
225 delete d;
226 d_ptr = nullptr;
227 }
228
229
from7BitString(const char * s,size_t len)230 void Structured::from7BitString(const char *s, size_t len)
231 {
232 Q_D(Structured);
233 if (d->encCS.isEmpty()) {
234 d->encCS = Content::defaultCharset();
235 }
236 parse(s, s + len);
237 }
238
from7BitString(const QByteArray & s)239 void Structured::from7BitString(const QByteArray &s)
240 {
241 #if 0
242 Q_D(Structured);
243 //Bug about mailto with space which are replaced by "_" so it failed to parse
244 //=> we reconvert to correct encoding as RFC2047
245 const QString str = KCodecs::decodeRFC2047String(s, &d->encCS, Content::defaultCharset());
246 const QByteArray ba = KCodecs::encodeRFC2047String(str, d->encCS);
247 from7BitString(ba.constData(), ba.length());
248 #else
249 from7BitString(s.constData(), s.length());
250 #endif
251 }
252
asUnicodeString() const253 QString Structured::asUnicodeString() const
254 {
255 return QString::fromLatin1(as7BitString(false));
256 }
257
fromUnicodeString(const QString & s,const QByteArray & b)258 void Structured::fromUnicodeString(const QString &s, const QByteArray &b)
259 {
260 Q_D(Structured);
261 d->encCS = cachedCharset(b);
262 from7BitString(s.toLatin1());
263 }
264
265 //-----</Structured>-------------------------
266
267 //-----<Address>-------------------------
268
Address()269 Address::Address() : Structured(new AddressPrivate)
270 {
271 }
272
kmime_mk_dptr_ctor(Address,Structured)273 kmime_mk_dptr_ctor(Address, Structured)
274
275 Address:: ~Address()
276 {
277 }
278
279 // helper method used in AddressList and MailboxList
stringToMailbox(const QByteArray & address,const QString & displayName,Types::Mailbox & mbox)280 static bool stringToMailbox(const QByteArray &address,
281 const QString &displayName, Types::Mailbox &mbox)
282 {
283 Types::AddrSpec addrSpec;
284 mbox.setName(displayName);
285 const char *cursor = address.constData();
286 if (!parseAngleAddr(cursor, cursor + address.length(), addrSpec)) {
287 if (!parseAddrSpec(cursor, cursor + address.length(), addrSpec)) {
288 qCWarning(KMIME_LOG) << "stringToMailbox: Invalid address";
289 return false;
290 }
291 }
292 mbox.setAddress(addrSpec);
293 return true;
294 }
295
296 //-----</Address>-------------------------
297
298 //-----<MailboxList>-------------------------
299
kmime_mk_trivial_ctor_with_dptr(MailboxList,Address)300 kmime_mk_trivial_ctor_with_dptr(MailboxList, Address)
301 kmime_mk_dptr_ctor(MailboxList, Address)
302
303 QByteArray MailboxList::as7BitString(bool withHeaderType) const
304 {
305 const Q_D(MailboxList);
306 if (isEmpty()) {
307 return QByteArray();
308 }
309
310 QByteArray rv;
311 if (withHeaderType) {
312 rv = typeIntro();
313 }
314 for (const Types::Mailbox &mbox : std::as_const(d->mailboxList)) {
315 rv += mbox.as7BitString(d->encCS);
316 rv += ", ";
317 }
318 rv.resize(rv.length() - 2);
319 return rv;
320 }
321
fromUnicodeString(const QString & s,const QByteArray & b)322 void MailboxList::fromUnicodeString(const QString &s, const QByteArray &b)
323 {
324 Q_D(MailboxList);
325 d->encCS = cachedCharset(b);
326 from7BitString(encodeRFC2047Sentence(s, b));
327 }
328
asUnicodeString() const329 QString MailboxList::asUnicodeString() const
330 {
331 Q_D(const MailboxList);
332 return Mailbox::listToUnicodeString(d->mailboxList);
333 }
334
clear()335 void MailboxList::clear()
336 {
337 Q_D(MailboxList);
338 d->mailboxList.clear();
339 }
340
isEmpty() const341 bool MailboxList::isEmpty() const
342 {
343 return d_func()->mailboxList.isEmpty();
344 }
345
addAddress(const Types::Mailbox & mbox)346 void MailboxList::addAddress(const Types::Mailbox &mbox)
347 {
348 Q_D(MailboxList);
349 d->mailboxList.append(mbox);
350 }
351
addAddress(const QByteArray & address,const QString & displayName)352 void MailboxList::addAddress(const QByteArray &address,
353 const QString &displayName)
354 {
355 Q_D(MailboxList);
356 Types::Mailbox mbox;
357 if (stringToMailbox(address, displayName, mbox)) {
358 d->mailboxList.append(mbox);
359 }
360 }
361
addresses() const362 QVector<QByteArray> MailboxList::addresses() const
363 {
364 QVector<QByteArray> rv;
365 rv.reserve(d_func()->mailboxList.count());
366 const auto mailboxList = d_func()->mailboxList;
367 for (const Types::Mailbox &mbox : mailboxList) {
368 rv.append(mbox.address());
369 }
370 return rv;
371 }
372
displayNames() const373 QStringList MailboxList::displayNames() const
374 {
375 Q_D(const MailboxList);
376 QStringList rv;
377 rv.reserve(d->mailboxList.count());
378 for (const Types::Mailbox &mbox : std::as_const(d->mailboxList)) {
379 if (mbox.hasName()) {
380 rv.append(mbox.name());
381 } else {
382 rv.append(QString::fromLatin1(mbox.address()));
383 }
384 }
385 return rv;
386 }
387
displayString() const388 QString MailboxList::displayString() const
389 {
390 Q_D(const MailboxList);
391 if (d->mailboxList.size() == 1) { // fast-path to avoid temporary QStringList in the common case of just one From address
392 const auto& mbox = d->mailboxList.at(0);
393 if (mbox.hasName()) {
394 return mbox.name();
395 } else {
396 return QString::fromLatin1(mbox.address());
397 }
398 }
399 return displayNames().join(QLatin1String(", "));
400 }
401
mailboxes() const402 Types::Mailbox::List MailboxList::mailboxes() const
403 {
404 return d_func()->mailboxList;
405 }
406
parse(const char * & scursor,const char * const send,bool isCRLF)407 bool MailboxList::parse(const char *&scursor, const char *const send,
408 bool isCRLF)
409 {
410 Q_D(MailboxList);
411 // examples:
412 // from := "From:" mailbox-list CRLF
413 // sender := "Sender:" mailbox CRLF
414
415 // parse an address-list:
416 QVector<Types::Address> maybeAddressList;
417 if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) {
418 return false;
419 }
420
421 d->mailboxList.clear();
422 d->mailboxList.reserve(maybeAddressList.count());
423
424 // extract the mailboxes and complain if there are groups:
425 for (const auto &it : std::as_const(maybeAddressList)) {
426 if (!(it).displayName.isEmpty()) {
427 KMIME_WARN << "mailbox groups in header disallowing them! Name: \""
428 << (it).displayName << "\""
429 << Qt::endl
430 ;
431 }
432 d->mailboxList += (it).mailboxList;
433 }
434 return true;
435 }
436
437 //-----</MailboxList>-------------------------
438
439 //-----<SingleMailbox>-------------------------
440
441 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(SingleMailbox,MailboxList)442 kmime_mk_trivial_ctor_with_dptr(SingleMailbox, MailboxList)
443 //@endcond
444
445 bool SingleMailbox::parse(const char *&scursor, const char *const send,
446 bool isCRLF)
447 {
448 Q_D(MailboxList);
449 if (!MailboxList::parse(scursor, send, isCRLF)) {
450 return false;
451 }
452
453 if (d->mailboxList.count() > 1) {
454 KMIME_WARN << "multiple mailboxes in header allowing only a single one!"
455 << Qt::endl;
456 }
457 return true;
458 }
459
460 //-----</SingleMailbox>-------------------------
461
462 //-----<AddressList>-------------------------
463
464 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(AddressList,Address)465 kmime_mk_trivial_ctor_with_dptr(AddressList, Address)
466 kmime_mk_dptr_ctor(AddressList, Address)
467 //@endcond
468
469 QByteArray AddressList::as7BitString(bool withHeaderType) const
470 {
471 const Q_D(AddressList);
472 if (d->addressList.isEmpty()) {
473 return QByteArray();
474 }
475
476 QByteArray rv;
477 if (withHeaderType) {
478 rv = typeIntro();
479 }
480 for (const Types::Address &addr : std::as_const(d->addressList)) {
481 const auto mailBoxList = addr.mailboxList;
482 for (const Types::Mailbox &mbox : mailBoxList) {
483 rv += mbox.as7BitString(d->encCS);
484 rv += ", ";
485 }
486 }
487 rv.resize(rv.length() - 2);
488 return rv;
489 }
490
fromUnicodeString(const QString & s,const QByteArray & b)491 void AddressList::fromUnicodeString(const QString &s, const QByteArray &b)
492 {
493 Q_D(AddressList);
494 d->encCS = cachedCharset(b);
495 from7BitString(encodeRFC2047Sentence(s, b));
496 }
497
asUnicodeString() const498 QString AddressList::asUnicodeString() const
499 {
500 Q_D(const AddressList);
501 QStringList rv;
502 for (const Types::Address &addr : std::as_const(d->addressList)) {
503 rv.reserve(rv.size() + addr.mailboxList.size());
504 const auto mailboxList = addr.mailboxList;
505 for (const Types::Mailbox &mbox : mailboxList) {
506 rv.append(mbox.prettyAddress());
507 }
508 }
509 return rv.join(QLatin1String(", "));
510 }
511
clear()512 void AddressList::clear()
513 {
514 Q_D(AddressList);
515 d->addressList.clear();
516 }
517
isEmpty() const518 bool AddressList::isEmpty() const
519 {
520 return d_func()->addressList.isEmpty();
521 }
522
addAddress(const Types::Mailbox & mbox)523 void AddressList::addAddress(const Types::Mailbox &mbox)
524 {
525 Q_D(AddressList);
526 Types::Address addr;
527 addr.mailboxList.append(mbox);
528 d->addressList.append(addr);
529 }
530
addAddress(const QByteArray & address,const QString & displayName)531 void AddressList::addAddress(const QByteArray &address,
532 const QString &displayName)
533 {
534 Q_D(AddressList);
535 Types::Address addr;
536 Types::Mailbox mbox;
537 if (stringToMailbox(address, displayName, mbox)) {
538 addr.mailboxList.append(mbox);
539 d->addressList.append(addr);
540 }
541 }
542
addresses() const543 QVector<QByteArray> AddressList::addresses() const
544 {
545 QVector<QByteArray> rv;
546 const auto addressList = d_func()->addressList;
547 for (const Types::Address &addr : addressList) {
548 const auto mailboxList = addr.mailboxList;
549 for (const Types::Mailbox &mbox : mailboxList) {
550 rv.append(mbox.address());
551 }
552 }
553 return rv;
554 }
555
displayNames() const556 QStringList AddressList::displayNames() const
557 {
558 Q_D(const AddressList);
559 QStringList rv;
560 for (const Types::Address &addr : std::as_const(d->addressList)) {
561 const auto mailboxList = addr.mailboxList;
562 for (const Types::Mailbox &mbox : mailboxList) {
563 if (mbox.hasName()) {
564 rv.append(mbox.name());
565 } else {
566 rv.append(QString::fromLatin1(mbox.address()));
567 }
568 }
569 }
570 return rv;
571 }
572
displayString() const573 QString AddressList::displayString() const
574 {
575 // optimize for single entry and avoid creation of the QStringList in that case?
576 return displayNames().join(QLatin1String(", "));
577 }
578
mailboxes() const579 Types::Mailbox::List AddressList::mailboxes() const
580 {
581 Types::Mailbox::List rv;
582 const auto addressList = d_func()->addressList;
583 for (const Types::Address &addr : addressList) {
584 const auto mailboxList = addr.mailboxList;
585 for (const Types::Mailbox &mbox : mailboxList) {
586 rv.append(mbox);
587 }
588 }
589 return rv;
590 }
591
parse(const char * & scursor,const char * const send,bool isCRLF)592 bool AddressList::parse(const char *&scursor, const char *const send,
593 bool isCRLF)
594 {
595 Q_D(AddressList);
596 QVector<Types::Address> maybeAddressList;
597 if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) {
598 return false;
599 }
600
601 d->addressList = maybeAddressList;
602 return true;
603 }
604
605 //-----</AddressList>-------------------------
606
607 //-----<Token>-------------------------
608
609 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(Token,Structured)610 kmime_mk_trivial_ctor_with_dptr(Token, Structured)
611 kmime_mk_dptr_ctor(Token, Structured)
612 //@endcond
613
614 QByteArray Token::as7BitString(bool withHeaderType) const
615 {
616 if (isEmpty()) {
617 return QByteArray();
618 }
619 if (withHeaderType) {
620 return typeIntro() + d_func()->token;
621 }
622 return d_func()->token;
623 }
624
clear()625 void Token::clear()
626 {
627 Q_D(Token);
628 d->token.clear();
629 }
630
isEmpty() const631 bool Token::isEmpty() const
632 {
633 return d_func()->token.isEmpty();
634 }
635
token() const636 QByteArray Token::token() const
637 {
638 return d_func()->token;
639 }
640
setToken(const QByteArray & t)641 void Token::setToken(const QByteArray &t)
642 {
643 Q_D(Token);
644 d->token = t;
645 }
646
parse(const char * & scursor,const char * const send,bool isCRLF)647 bool Token::parse(const char *&scursor, const char *const send, bool isCRLF)
648 {
649 Q_D(Token);
650 clear();
651 eatCFWS(scursor, send, isCRLF);
652 // must not be empty:
653 if (scursor == send) {
654 return false;
655 }
656
657 QPair<const char *, int> maybeToken;
658 if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) {
659 return false;
660 }
661 d->token = QByteArray(maybeToken.first, maybeToken.second);
662
663 // complain if trailing garbage is found:
664 eatCFWS(scursor, send, isCRLF);
665 if (scursor != send) {
666 KMIME_WARN << "trailing garbage after token in header allowing "
667 "only a single token!"
668 << Qt::endl;
669 }
670 return true;
671 }
672
673 //-----</Token>-------------------------
674
675 //-----<PhraseList>-------------------------
676
677 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(PhraseList,Structured)678 kmime_mk_trivial_ctor_with_dptr(PhraseList, Structured)
679 //@endcond
680
681 QByteArray PhraseList::as7BitString(bool withHeaderType) const
682 {
683 const Q_D(PhraseList);
684 if (isEmpty()) {
685 return QByteArray();
686 }
687
688 QByteArray rv;
689 if (withHeaderType) {
690 rv = typeIntro();
691 }
692
693 for (int i = 0; i < d->phraseList.count(); ++i) {
694 // FIXME: only encode when needed, quote when needed, etc.
695 rv += encodeRFC2047String(d->phraseList[i], d->encCS, false, false);
696 if (i != d->phraseList.count() - 1) {
697 rv += ", ";
698 }
699 }
700
701 return rv;
702 }
703
asUnicodeString() const704 QString PhraseList::asUnicodeString() const
705 {
706 return d_func()->phraseList.join(QLatin1String(", "));
707 }
708
clear()709 void PhraseList::clear()
710 {
711 Q_D(PhraseList);
712 d->phraseList.clear();
713 }
714
isEmpty() const715 bool PhraseList::isEmpty() const
716 {
717 return d_func()->phraseList.isEmpty();
718 }
719
phrases() const720 QStringList PhraseList::phrases() const
721 {
722 return d_func()->phraseList;
723 }
724
parse(const char * & scursor,const char * const send,bool isCRLF)725 bool PhraseList::parse(const char *&scursor, const char *const send,
726 bool isCRLF)
727 {
728 Q_D(PhraseList);
729 d->phraseList.clear();
730
731 while (scursor != send) {
732 eatCFWS(scursor, send, isCRLF);
733 // empty entry ending the list: OK.
734 if (scursor == send) {
735 return true;
736 }
737 // empty entry: ignore.
738 if (*scursor == ',') {
739 scursor++;
740 continue;
741 }
742
743 QString maybePhrase;
744 if (!parsePhrase(scursor, send, maybePhrase, isCRLF)) {
745 return false;
746 }
747 d->phraseList.append(maybePhrase);
748
749 eatCFWS(scursor, send, isCRLF);
750 // non-empty entry ending the list: OK.
751 if (scursor == send) {
752 return true;
753 }
754 // comma separating the phrases: eat.
755 if (*scursor == ',') {
756 scursor++;
757 }
758 }
759 return true;
760 }
761
762 //-----</PhraseList>-------------------------
763
764 //-----<DotAtom>-------------------------
765
766 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(DotAtom,Structured)767 kmime_mk_trivial_ctor_with_dptr(DotAtom, Structured)
768 //@endcond
769
770 QByteArray DotAtom::as7BitString(bool withHeaderType) const
771 {
772 if (isEmpty()) {
773 return QByteArray();
774 }
775
776 QByteArray rv;
777 if (withHeaderType) {
778 rv += typeIntro();
779 }
780
781 rv += d_func()->dotAtom;
782 return rv;
783 }
784
asUnicodeString() const785 QString DotAtom::asUnicodeString() const
786 {
787 return QString::fromLatin1(d_func()->dotAtom);
788 }
789
clear()790 void DotAtom::clear()
791 {
792 Q_D(DotAtom);
793 d->dotAtom.clear();
794 }
795
isEmpty() const796 bool DotAtom::isEmpty() const
797 {
798 return d_func()->dotAtom.isEmpty();
799 }
800
parse(const char * & scursor,const char * const send,bool isCRLF)801 bool DotAtom::parse(const char *&scursor, const char *const send,
802 bool isCRLF)
803 {
804 Q_D(DotAtom);
805 QByteArray maybeDotAtom;
806 if (!parseDotAtom(scursor, send, maybeDotAtom, isCRLF)) {
807 return false;
808 }
809
810 d->dotAtom = maybeDotAtom;
811
812 eatCFWS(scursor, send, isCRLF);
813 if (scursor != send) {
814 KMIME_WARN << "trailing garbage after dot-atom in header allowing "
815 "only a single dot-atom!"
816 << Qt::endl;
817 }
818 return true;
819 }
820
821 //-----</DotAtom>-------------------------
822
823 //-----<Parametrized>-------------------------
824
825 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(Parametrized,Structured)826 kmime_mk_trivial_ctor_with_dptr(Parametrized, Structured)
827 kmime_mk_dptr_ctor(Parametrized, Structured)
828 //@endcond
829
830 QByteArray Parametrized::as7BitString(bool withHeaderType) const
831 {
832 const Q_D(Parametrized);
833 if (isEmpty()) {
834 return QByteArray();
835 }
836
837 QByteArray rv;
838 if (withHeaderType) {
839 rv += typeIntro();
840 }
841
842 bool first = true;
843 for (QMap<QString, QString>::ConstIterator it = d->parameterHash.constBegin();
844 it != d->parameterHash.constEnd(); ++it) {
845 if (!first) {
846 rv += "; ";
847 } else {
848 first = false;
849 }
850 if (isUsAscii(it.value())) {
851 rv += it.key().toLatin1() + '=';
852 QByteArray tmp = it.value().toLatin1();
853 addQuotes(tmp, true); // force quoting, eg. for whitespaces in parameter value
854 rv += tmp;
855 } else {
856 if (useOutlookAttachmentEncoding()) {
857 rv += it.key().toLatin1() + '=';
858 qCDebug(KMIME_LOG) << "doing:" << it.value() << QLatin1String(d->encCS);
859 rv += "\"" + encodeRFC2047String(it.value(), d->encCS) + "\"";
860 } else {
861 rv += it.key().toLatin1() + "*=";
862 rv += encodeRFC2231String(it.value(), d->encCS);
863 }
864 }
865 }
866
867 return rv;
868 }
869
parameter(const QString & key) const870 QString Parametrized::parameter(const QString &key) const
871 {
872 return d_func()->parameterHash.value(key.toLower());
873 }
874
hasParameter(const QString & key) const875 bool Parametrized::hasParameter(const QString &key) const
876 {
877 return d_func()->parameterHash.contains(key.toLower());
878 }
879
setParameter(const QString & key,const QString & value)880 void Parametrized::setParameter(const QString &key, const QString &value)
881 {
882 Q_D(Parametrized);
883 d->parameterHash.insert(key.toLower(), value);
884 }
885
isEmpty() const886 bool Parametrized::isEmpty() const
887 {
888 return d_func()->parameterHash.isEmpty();
889 }
890
clear()891 void Parametrized::clear()
892 {
893 Q_D(Parametrized);
894 d->parameterHash.clear();
895 }
896
parse(const char * & scursor,const char * const send,bool isCRLF)897 bool Parametrized::parse(const char *&scursor, const char *const send,
898 bool isCRLF)
899 {
900 Q_D(Parametrized);
901 d->parameterHash.clear();
902 QByteArray charset;
903 if (!parseParameterListWithCharset(scursor, send, d->parameterHash, charset, isCRLF)) {
904 return false;
905 }
906 d->encCS = charset;
907 return true;
908 }
909
910 //-----</Parametrized>-------------------------
911
912 //-----<Ident>-------------------------
913
914 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(Ident,Address)915 kmime_mk_trivial_ctor_with_dptr(Ident, Address)
916 kmime_mk_dptr_ctor(Ident, Address)
917 //@endcond
918
919 QByteArray Ident::as7BitString(bool withHeaderType) const
920 {
921 const Q_D(Ident);
922 if (d->msgIdList.isEmpty()) {
923 return QByteArray();
924 }
925
926 QByteArray rv;
927 if (withHeaderType) {
928 rv = typeIntro();
929 }
930 for (const Types::AddrSpec &addr : std::as_const(d->msgIdList)) {
931 if (!addr.isEmpty()) {
932 const QString asString = addr.asString();
933 rv += '<';
934 if (!asString.isEmpty()) {
935 rv += asString.toLatin1(); // FIXME: change parsing to use QByteArrays
936 }
937 rv += "> ";
938 }
939 }
940 if (!rv.isEmpty()) {
941 rv.resize(rv.length() - 1);
942 }
943 return rv;
944 }
945
clear()946 void Ident::clear()
947 {
948 Q_D(Ident);
949 d->msgIdList.clear();
950 d->cachedIdentifier.clear();
951 }
952
isEmpty() const953 bool Ident::isEmpty() const
954 {
955 return d_func()->msgIdList.isEmpty();
956 }
957
parse(const char * & scursor,const char * const send,bool isCRLF)958 bool Ident::parse(const char *&scursor, const char *const send, bool isCRLF)
959 {
960 Q_D(Ident);
961 // msg-id := "<" id-left "@" id-right ">"
962 // id-left := dot-atom-text / no-fold-quote / local-part
963 // id-right := dot-atom-text / no-fold-literal / domain
964 //
965 // equivalent to:
966 // msg-id := angle-addr
967
968 d->msgIdList.clear();
969 d->cachedIdentifier.clear();
970
971 while (scursor != send) {
972 eatCFWS(scursor, send, isCRLF);
973 // empty entry ending the list: OK.
974 if (scursor == send) {
975 return true;
976 }
977 // empty entry: ignore.
978 if (*scursor == ',') {
979 scursor++;
980 continue;
981 }
982
983 AddrSpec maybeMsgId;
984 if (!parseAngleAddr(scursor, send, maybeMsgId, isCRLF)) {
985 return false;
986 }
987 d->msgIdList.append(maybeMsgId);
988
989 eatCFWS(scursor, send, isCRLF);
990 // header end ending the list: OK.
991 if (scursor == send) {
992 return true;
993 }
994 // regular item separator: eat it.
995 if (*scursor == ',') {
996 scursor++;
997 }
998 }
999 return true;
1000 }
1001
identifiers() const1002 QVector<QByteArray> Ident::identifiers() const
1003 {
1004 QVector<QByteArray> rv;
1005 const auto msgIdList = d_func()->msgIdList;
1006 for (const Types::AddrSpec &addr : msgIdList) {
1007 if (!addr.isEmpty()) {
1008 const QString asString = addr.asString();
1009 if (!asString.isEmpty()) {
1010 rv.append(asString.toLatin1()); // FIXME: change parsing to use QByteArrays
1011 }
1012 }
1013 }
1014 return rv;
1015 }
1016
fromIdent(const Ident * ident)1017 void Ident::fromIdent(const Ident* ident)
1018 {
1019 d_func()->encCS = ident->d_func()->encCS;
1020 d_func()->msgIdList = ident->d_func()->msgIdList;
1021 d_func()->cachedIdentifier = ident->d_func()->cachedIdentifier;
1022 }
1023
appendIdentifier(const QByteArray & id)1024 void Ident::appendIdentifier(const QByteArray &id)
1025 {
1026 Q_D(Ident);
1027 QByteArray tmp = id;
1028 if (!tmp.startsWith('<')) {
1029 tmp.prepend('<');
1030 }
1031 if (!tmp.endsWith('>')) {
1032 tmp.append('>');
1033 }
1034 AddrSpec msgId;
1035 const char *cursor = tmp.constData();
1036 if (parseAngleAddr(cursor, cursor + tmp.length(), msgId)) {
1037 d->msgIdList.append(msgId);
1038 } else {
1039 qCWarning(KMIME_LOG) << "Unable to parse address spec!";
1040 }
1041 }
1042
1043 //-----</Ident>-------------------------
1044
1045 //-----<SingleIdent>-------------------------
1046
1047 //@cond PRIVATE
kmime_mk_trivial_ctor_with_dptr(SingleIdent,Ident)1048 kmime_mk_trivial_ctor_with_dptr(SingleIdent, Ident)
1049 kmime_mk_dptr_ctor(SingleIdent, Ident)
1050 //@endcond
1051
1052 QByteArray SingleIdent::identifier() const
1053 {
1054 if (d_func()->msgIdList.isEmpty()) {
1055 return QByteArray();
1056 }
1057
1058 if (d_func()->cachedIdentifier.isEmpty()) {
1059 const Types::AddrSpec &addr = d_func()->msgIdList.first();
1060 if (!addr.isEmpty()) {
1061 const QString asString = addr.asString();
1062 if (!asString.isEmpty()) {
1063 d_func()->cachedIdentifier = asString.toLatin1();// FIXME: change parsing to use QByteArrays
1064 }
1065 }
1066 }
1067
1068 return d_func()->cachedIdentifier;
1069 }
1070
setIdentifier(const QByteArray & id)1071 void SingleIdent::setIdentifier(const QByteArray &id)
1072 {
1073 Q_D(SingleIdent);
1074 d->msgIdList.clear();
1075 d->cachedIdentifier.clear();
1076 appendIdentifier(id);
1077 }
1078
parse(const char * & scursor,const char * const send,bool isCRLF)1079 bool SingleIdent::parse(const char *&scursor, const char *const send,
1080 bool isCRLF)
1081 {
1082 Q_D(SingleIdent);
1083 if (!Ident::parse(scursor, send, isCRLF)) {
1084 return false;
1085 }
1086
1087 if (d->msgIdList.count() > 1) {
1088 KMIME_WARN << "more than one msg-id in header "
1089 << "allowing only a single one!"
1090 << Qt::endl;
1091 }
1092 return true;
1093 }
1094
1095 //-----</SingleIdent>-------------------------
1096
1097 } // namespace Generics
1098
1099 //-----<ReturnPath>-------------------------
1100
1101 //@cond PRIVATE
1102 kmime_mk_trivial_ctor_with_name_and_dptr(ReturnPath, Generics::Address, Return-Path)
1103 //@endcond
1104
as7BitString(bool withHeaderType) const1105 QByteArray ReturnPath::as7BitString(bool withHeaderType) const
1106 {
1107 if (isEmpty()) {
1108 return QByteArray();
1109 }
1110
1111 QByteArray rv;
1112 if (withHeaderType) {
1113 rv += typeIntro();
1114 }
1115 rv += '<' + d_func()->mailbox.as7BitString(d_func()->encCS) + '>';
1116 return rv;
1117 }
1118
clear()1119 void ReturnPath::clear()
1120 {
1121 Q_D(ReturnPath);
1122 d->mailbox.setAddress(Types::AddrSpec());
1123 d->mailbox.setName(QString());
1124 }
1125
isEmpty() const1126 bool ReturnPath::isEmpty() const
1127 {
1128 const Q_D(ReturnPath);
1129 return !d->mailbox.hasAddress() && !d->mailbox.hasName();
1130 }
1131
parse(const char * & scursor,const char * const send,bool isCRLF)1132 bool ReturnPath::parse(const char *&scursor, const char *const send,
1133 bool isCRLF)
1134 {
1135 Q_D(ReturnPath);
1136 eatCFWS(scursor, send, isCRLF);
1137 if (scursor == send) {
1138 return false;
1139 }
1140
1141 const char *oldscursor = scursor;
1142
1143 Mailbox maybeMailbox;
1144 if (!parseMailbox(scursor, send, maybeMailbox, isCRLF)) {
1145 // mailbox parsing failed, but check for empty brackets:
1146 scursor = oldscursor;
1147 if (*scursor != '<') {
1148 return false;
1149 }
1150 scursor++;
1151 eatCFWS(scursor, send, isCRLF);
1152 if (scursor == send || *scursor != '>') {
1153 return false;
1154 }
1155 scursor++;
1156
1157 // prepare a Null mailbox:
1158 AddrSpec emptyAddrSpec;
1159 maybeMailbox.setName(QString());
1160 maybeMailbox.setAddress(emptyAddrSpec);
1161 } else {
1162 // check that there was no display-name:
1163 if (maybeMailbox.hasName()) {
1164 KMIME_WARN << "display-name \"" << maybeMailbox.name()
1165 << "\" in Return-Path!" << Qt::endl;
1166 }
1167 }
1168 d->mailbox = maybeMailbox;
1169
1170 // see if that was all:
1171 eatCFWS(scursor, send, isCRLF);
1172 // and warn if it wasn't:
1173 if (scursor != send) {
1174 KMIME_WARN << "trailing garbage after angle-addr in Return-Path!"
1175 << Qt::endl;
1176 }
1177 return true;
1178 }
1179
1180 //-----</ReturnPath>-------------------------
1181
1182 //-----<Generic>-------------------------------
1183
1184 // NOTE: Do *not* register Generic with HeaderFactory, since its type() is changeable.
1185
Generic()1186 Generic::Generic() : Generics::Unstructured(new GenericPrivate)
1187 {
1188 }
1189
Generic(const char * t,int len)1190 Generic::Generic(const char *t, int len) : Generics::Unstructured(new GenericPrivate)
1191 {
1192 setType(t, len);
1193 }
1194
~Generic()1195 Generic::~Generic()
1196 {
1197 Q_D(Generic);
1198 delete d;
1199 d_ptr = nullptr;
1200 }
1201
clear()1202 void Generic::clear()
1203 {
1204 Q_D(Generic);
1205 delete[] d->type;
1206 d->type = nullptr;
1207 Unstructured::clear();
1208 }
1209
isEmpty() const1210 bool Generic::isEmpty() const
1211 {
1212 return d_func()->type == nullptr || Unstructured::isEmpty();
1213 }
1214
type() const1215 const char *Generic::type() const
1216 {
1217 return d_func()->type;
1218 }
1219
setType(const char * type,int len)1220 void Generic::setType(const char *type, int len)
1221 {
1222 Q_D(Generic);
1223 if (d->type) {
1224 delete[] d->type;
1225 }
1226 if (type) {
1227 const int l = (len < 0 ? strlen(type) : len) + 1;
1228 d->type = new char[l];
1229 qstrncpy(d->type, type, l);
1230 } else {
1231 d->type = nullptr;
1232 }
1233 }
1234
1235 //-----<Generic>-------------------------------
1236
1237 //-----<MessageID>-----------------------------
1238
1239 //@cond PRIVATE
1240 kmime_mk_trivial_ctor_with_name(MessageID, Generics::SingleIdent, Message-ID)
1241 //@endcond
1242
generate(const QByteArray & fqdn)1243 void MessageID::generate(const QByteArray &fqdn)
1244 {
1245 setIdentifier('<' + uniqueString() + '@' + fqdn + '>');
1246 }
1247
1248 //-----</MessageID>----------------------------
1249
1250 //-----<Control>-------------------------------
1251
1252 //@cond PRIVATE
kmime_mk_trivial_ctor_with_name_and_dptr(Control,Generics::Structured,Control)1253 kmime_mk_trivial_ctor_with_name_and_dptr(Control, Generics::Structured, Control)
1254 //@endcond
1255
1256 QByteArray Control::as7BitString(bool withHeaderType) const
1257 {
1258 const Q_D(Control);
1259 if (isEmpty()) {
1260 return QByteArray();
1261 }
1262
1263 QByteArray rv;
1264 if (withHeaderType) {
1265 rv += typeIntro();
1266 }
1267
1268 rv += d->name;
1269 if (!d->parameter.isEmpty()) {
1270 rv += ' ' + d->parameter;
1271 }
1272 return rv;
1273 }
1274
clear()1275 void Control::clear()
1276 {
1277 Q_D(Control);
1278 d->name.clear();
1279 d->parameter.clear();
1280 }
1281
isEmpty() const1282 bool Control::isEmpty() const
1283 {
1284 return d_func()->name.isEmpty();
1285 }
1286
controlType() const1287 QByteArray Control::controlType() const
1288 {
1289 return d_func()->name;
1290 }
1291
parameter() const1292 QByteArray Control::parameter() const
1293 {
1294 return d_func()->parameter;
1295 }
1296
isCancel() const1297 bool Control::isCancel() const
1298 {
1299 return d_func()->name.toLower() == "cancel";
1300 }
1301
setCancel(const QByteArray & msgid)1302 void Control::setCancel(const QByteArray &msgid)
1303 {
1304 Q_D(Control);
1305 d->name = "cancel";
1306 d->parameter = msgid;
1307 }
1308
parse(const char * & scursor,const char * const send,bool isCRLF)1309 bool Control::parse(const char *&scursor, const char *const send, bool isCRLF)
1310 {
1311 Q_D(Control);
1312 clear();
1313 eatCFWS(scursor, send, isCRLF);
1314 if (scursor == send) {
1315 return false;
1316 }
1317 const char *start = scursor;
1318 while (scursor != send && !isspace(*scursor)) {
1319 ++scursor;
1320 }
1321 d->name = QByteArray(start, scursor - start);
1322 eatCFWS(scursor, send, isCRLF);
1323 d->parameter = QByteArray(scursor, send - scursor);
1324 return true;
1325 }
1326
1327 //-----</Control>------------------------------
1328
1329 //-----<MailCopiesTo>--------------------------
1330
1331 //@cond PRIVATE
1332 kmime_mk_trivial_ctor_with_name_and_dptr(MailCopiesTo,
1333 Generics::AddressList, Mail-Copies-To)
1334 //@endcond
1335
as7BitString(bool withHeaderType) const1336 QByteArray MailCopiesTo::as7BitString(bool withHeaderType) const
1337 {
1338 QByteArray rv;
1339 if (withHeaderType) {
1340 rv += typeIntro();
1341 }
1342 if (!AddressList::isEmpty()) {
1343 rv += AddressList::as7BitString(false);
1344 } else {
1345 if (d_func()->alwaysCopy) {
1346 rv += "poster";
1347 } else if (d_func()->neverCopy) {
1348 rv += "nobody";
1349 }
1350 }
1351 return rv;
1352 }
1353
asUnicodeString() const1354 QString MailCopiesTo::asUnicodeString() const
1355 {
1356 if (!AddressList::isEmpty()) {
1357 return AddressList::asUnicodeString();
1358 }
1359 if (d_func()->alwaysCopy) {
1360 return QStringLiteral("poster");
1361 }
1362 if (d_func()->neverCopy) {
1363 return QStringLiteral("nobody");
1364 }
1365 return QString();
1366 }
1367
clear()1368 void MailCopiesTo::clear()
1369 {
1370 Q_D(MailCopiesTo);
1371 AddressList::clear();
1372 d->alwaysCopy = false;
1373 d->neverCopy = false;
1374 }
1375
isEmpty() const1376 bool MailCopiesTo::isEmpty() const
1377 {
1378 return AddressList::isEmpty() && !(d_func()->alwaysCopy || d_func()->neverCopy);
1379 }
1380
alwaysCopy() const1381 bool MailCopiesTo::alwaysCopy() const
1382 {
1383 return !AddressList::isEmpty() || d_func()->alwaysCopy;
1384 }
1385
setAlwaysCopy()1386 void MailCopiesTo::setAlwaysCopy()
1387 {
1388 Q_D(MailCopiesTo);
1389 clear();
1390 d->alwaysCopy = true;
1391 }
1392
neverCopy() const1393 bool MailCopiesTo::neverCopy() const
1394 {
1395 return d_func()->neverCopy;
1396 }
1397
setNeverCopy()1398 void MailCopiesTo::setNeverCopy()
1399 {
1400 Q_D(MailCopiesTo);
1401 clear();
1402 d->neverCopy = true;
1403 }
1404
parse(const char * & scursor,const char * const send,bool isCRLF)1405 bool MailCopiesTo::parse(const char *&scursor, const char *const send,
1406 bool isCRLF)
1407 {
1408 Q_D(MailCopiesTo);
1409 clear();
1410 if (send - scursor == 5) {
1411 if (qstrnicmp("never", scursor, 5) == 0) {
1412 d->neverCopy = true;
1413 return true;
1414 }
1415 }
1416 if (send - scursor == 6) {
1417 if (qstrnicmp("always", scursor, 6) == 0 || qstrnicmp("poster", scursor, 6) == 0) {
1418 d->alwaysCopy = true;
1419 return true;
1420 }
1421 if (qstrnicmp("nobody", scursor, 6) == 0) {
1422 d->neverCopy = true;
1423 return true;
1424 }
1425 }
1426 return AddressList::parse(scursor, send, isCRLF);
1427 }
1428
1429 //-----</MailCopiesTo>-------------------------
1430
1431 //-----<Date>----------------------------------
1432
1433 //@cond PRIVATE
kmime_mk_trivial_ctor_with_name_and_dptr(Date,Generics::Structured,Date)1434 kmime_mk_trivial_ctor_with_name_and_dptr(Date, Generics::Structured, Date)
1435 //@endcond
1436
1437 QByteArray Date::as7BitString(bool withHeaderType) const
1438 {
1439 if (isEmpty()) {
1440 return QByteArray();
1441 }
1442
1443 QByteArray rv;
1444 if (withHeaderType) {
1445 rv += typeIntro();
1446 }
1447 //QT5 fix port to QDateTime Qt::RFC2822Date is not enough we need to fix it. We need to use QLocale("C") + add "ddd, ";
1448 //rv += d_func()->dateTime.toString( Qt::RFC2822Date ).toLatin1();
1449 rv += QLocale::c().toString(d_func()->dateTime, QStringLiteral("ddd, ")).toLatin1();
1450 rv += d_func()->dateTime.toString(Qt::RFC2822Date).toLatin1();
1451
1452 return rv;
1453 }
1454
clear()1455 void Date::clear() {
1456 Q_D(Date);
1457 d->dateTime = QDateTime();
1458 }
1459
isEmpty() const1460 bool Date::isEmpty() const {
1461 return d_func()->dateTime.isNull() || !d_func()->dateTime.isValid();
1462 }
1463
dateTime() const1464 QDateTime Date::dateTime() const {
1465 return d_func()->dateTime;
1466 }
1467
setDateTime(const QDateTime & dt)1468 void Date::setDateTime(const QDateTime & dt) {
1469 Q_D(Date);
1470 d->dateTime = dt;
1471 }
1472
ageInDays() const1473 int Date::ageInDays() const {
1474 QDate today = QDate::currentDate();
1475 return dateTime().date().daysTo(today);
1476 }
1477
parse(const char * & scursor,const char * const send,bool isCRLF)1478 bool Date::parse(const char *&scursor, const char *const send, bool isCRLF) {
1479 Q_D(Date);
1480 return parseDateTime(scursor, send, d->dateTime, isCRLF);
1481 }
1482
1483 //-----</Date>---------------------------------
1484
1485 //-----<Newsgroups>----------------------------
1486
1487 //@cond PRIVATE
kmime_mk_trivial_ctor_with_name_and_dptr(Newsgroups,Generics::Structured,Newsgroups)1488 kmime_mk_trivial_ctor_with_name_and_dptr(Newsgroups, Generics::Structured, Newsgroups)
1489 kmime_mk_trivial_ctor_with_name(FollowUpTo, Newsgroups, Followup-To)
1490 //@endcond
1491
1492 QByteArray Newsgroups::as7BitString(bool withHeaderType) const {
1493 const Q_D(Newsgroups);
1494 if (isEmpty()) {
1495 return QByteArray();
1496 }
1497
1498 QByteArray rv;
1499 if (withHeaderType) {
1500 rv += typeIntro();
1501 }
1502
1503 for (int i = 0; i < d->groups.count(); ++i) {
1504 rv += d->groups[ i ];
1505 if (i != d->groups.count() - 1) {
1506 rv += ',';
1507 }
1508 }
1509 return rv;
1510 }
1511
fromUnicodeString(const QString & s,const QByteArray & b)1512 void Newsgroups::fromUnicodeString(const QString & s, const QByteArray & b) {
1513 Q_UNUSED(b)
1514 Q_D(Newsgroups);
1515 from7BitString(s.toUtf8());
1516 d->encCS = cachedCharset("UTF-8");
1517 }
1518
asUnicodeString() const1519 QString Newsgroups::asUnicodeString() const {
1520 return QString::fromUtf8(as7BitString(false));
1521 }
1522
clear()1523 void Newsgroups::clear() {
1524 Q_D(Newsgroups);
1525 d->groups.clear();
1526 }
1527
isEmpty() const1528 bool Newsgroups::isEmpty() const {
1529 return d_func()->groups.isEmpty();
1530 }
1531
groups() const1532 QVector<QByteArray> Newsgroups::groups() const {
1533 return d_func()->groups;
1534 }
1535
setGroups(const QVector<QByteArray> & groups)1536 void Newsgroups::setGroups(const QVector<QByteArray> &groups) {
1537 Q_D(Newsgroups);
1538 d->groups = groups;
1539 }
1540
isCrossposted() const1541 bool Newsgroups::isCrossposted() const {
1542 return d_func()->groups.count() >= 2;
1543 }
1544
parse(const char * & scursor,const char * const send,bool isCRLF)1545 bool Newsgroups::parse(const char *&scursor, const char *const send, bool isCRLF) {
1546 Q_D(Newsgroups);
1547 clear();
1548 while (true) {
1549 eatCFWS(scursor, send, isCRLF);
1550 if (scursor != send && *scursor == ',') {
1551 ++scursor;
1552 }
1553 eatCFWS(scursor, send, isCRLF);
1554 if (scursor == send) {
1555 return true;
1556 }
1557 const char *start = scursor;
1558 while (scursor != send && !isspace(*scursor) && *scursor != ',') {
1559 ++scursor;
1560 }
1561 QByteArray group(start, scursor - start);
1562 d->groups.append(group);
1563 }
1564 return true;
1565 }
1566
1567 //-----</Newsgroups>---------------------------
1568
1569 //-----<Lines>---------------------------------
1570
1571 //@cond PRIVATE
kmime_mk_trivial_ctor_with_name_and_dptr(Lines,Generics::Structured,Lines)1572 kmime_mk_trivial_ctor_with_name_and_dptr(Lines, Generics::Structured, Lines)
1573 //@endcond
1574
1575 QByteArray Lines::as7BitString(bool withHeaderType) const {
1576 if (isEmpty()) {
1577 return QByteArray();
1578 }
1579
1580 QByteArray num;
1581 num.setNum(d_func()->lines);
1582
1583 if (withHeaderType) {
1584 return typeIntro() + num;
1585 }
1586 return num;
1587 }
1588
asUnicodeString() const1589 QString Lines::asUnicodeString() const {
1590 if (isEmpty()) {
1591 return QString();
1592 }
1593 return QString::number(d_func()->lines);
1594 }
1595
clear()1596 void Lines::clear() {
1597 Q_D(Lines);
1598 d->lines = -1;
1599 }
1600
isEmpty() const1601 bool Lines::isEmpty() const {
1602 return d_func()->lines == -1;
1603 }
1604
numberOfLines() const1605 int Lines::numberOfLines() const {
1606 return d_func()->lines;
1607 }
1608
setNumberOfLines(int lines)1609 void Lines::setNumberOfLines(int lines) {
1610 Q_D(Lines);
1611 d->lines = lines;
1612 }
1613
parse(const char * & scursor,const char * const send,bool isCRLF)1614 bool Lines::parse(const char *&scursor, const char *const send, bool isCRLF) {
1615 Q_D(Lines);
1616 eatCFWS(scursor, send, isCRLF);
1617 if (parseDigits(scursor, send, d->lines) == 0) {
1618 clear();
1619 return false;
1620 }
1621 return true;
1622 }
1623
1624 //-----</Lines>--------------------------------
1625
1626 //-----<Content-Type>--------------------------
1627
1628 //@cond PRIVATE
1629 kmime_mk_trivial_ctor_with_name_and_dptr(ContentType, Generics::Parametrized,
1630 Content-Type)
1631 //@endcond
1632
isEmpty() const1633 bool ContentType::isEmpty() const {
1634 return d_func()->mimeType.isEmpty();
1635 }
1636
clear()1637 void ContentType::clear() {
1638 Q_D(ContentType);
1639 d->category = CCsingle;
1640 d->mimeType.clear();
1641 Parametrized::clear();
1642 }
1643
as7BitString(bool withHeaderType) const1644 QByteArray ContentType::as7BitString(bool withHeaderType) const {
1645 if (isEmpty()) {
1646 return QByteArray();
1647 }
1648
1649 QByteArray rv;
1650 if (withHeaderType) {
1651 rv += typeIntro();
1652 }
1653
1654 rv += mimeType();
1655 if (!Parametrized::isEmpty()) {
1656 rv += "; " + Parametrized::as7BitString(false);
1657 }
1658
1659 return rv;
1660 }
1661
mimeType() const1662 QByteArray ContentType::mimeType() const {
1663 Q_D(const ContentType);
1664 return d->mimeType;
1665 }
1666
mediaType() const1667 QByteArray ContentType::mediaType() const {
1668 Q_D(const ContentType);
1669 const int pos = d->mimeType.indexOf('/');
1670 if (pos < 0) {
1671 return d->mimeType;
1672 } else {
1673 return d->mimeType.left(pos);
1674 }
1675 }
1676
subType() const1677 QByteArray ContentType::subType() const {
1678 Q_D(const ContentType);
1679 const int pos = d->mimeType.indexOf('/');
1680 if (pos < 0) {
1681 return QByteArray();
1682 } else {
1683 return d->mimeType.mid(pos + 1);
1684 }
1685 }
1686
setMimeType(const QByteArray & mimeType)1687 void ContentType::setMimeType(const QByteArray & mimeType) {
1688 Q_D(ContentType);
1689 d->mimeType = mimeType;
1690
1691 if (isMultipart()) {
1692 d->category = CCcontainer;
1693 } else {
1694 d->category = CCsingle;
1695 }
1696 }
1697
isMediatype(const char * mediatype) const1698 bool ContentType::isMediatype(const char *mediatype) const {
1699 Q_D(const ContentType);
1700 const int len = strlen(mediatype);
1701 return qstrnicmp(d->mimeType.constData(), mediatype, len) == 0 &&
1702 (d->mimeType.at(len) == '/' || d->mimeType.size() == len);
1703 }
1704
isSubtype(const char * subtype) const1705 bool ContentType::isSubtype(const char *subtype) const {
1706 Q_D(const ContentType);
1707 const int pos = d->mimeType.indexOf('/');
1708 if (pos < 0) {
1709 return false;
1710 }
1711 const int len = strlen(subtype);
1712 return qstrnicmp(d->mimeType.constData() + pos + 1, subtype, len) == 0 &&
1713 d->mimeType.size() == pos + len + 1;
1714 }
1715
isMimeType(const char * mimeType) const1716 bool ContentType::isMimeType(const char* mimeType) const
1717 {
1718 Q_D(const ContentType);
1719 return qstricmp(d->mimeType.constData(), mimeType) == 0;
1720 }
1721
isText() const1722 bool ContentType::isText() const {
1723 return (isMediatype("text") || isEmpty());
1724 }
1725
isPlainText() const1726 bool ContentType::isPlainText() const {
1727 return (qstricmp(d_func()->mimeType.constData(), "text/plain") == 0 || isEmpty());
1728 }
1729
isHTMLText() const1730 bool ContentType::isHTMLText() const {
1731 return qstricmp(d_func()->mimeType.constData(), "text/html") == 0;
1732 }
1733
isImage() const1734 bool ContentType::isImage() const {
1735 return isMediatype("image");
1736 }
1737
isMultipart() const1738 bool ContentType::isMultipart() const {
1739 return isMediatype("multipart");
1740 }
1741
isPartial() const1742 bool ContentType::isPartial() const {
1743 return qstricmp(d_func()->mimeType.constData(), "message/partial") == 0;
1744 }
1745
charset() const1746 QByteArray ContentType::charset() const {
1747 QByteArray ret = parameter(QStringLiteral("charset")).toLatin1();
1748 if (ret.isEmpty()) {
1749 //return the default-charset if necessary
1750 ret = Content::defaultCharset();
1751 }
1752 return ret;
1753 }
1754
setCharset(const QByteArray & s)1755 void ContentType::setCharset(const QByteArray & s) {
1756 setParameter(QStringLiteral("charset"), QString::fromLatin1(s));
1757 }
1758
boundary() const1759 QByteArray ContentType::boundary() const {
1760 return parameter(QStringLiteral("boundary")).toLatin1();
1761 }
1762
setBoundary(const QByteArray & s)1763 void ContentType::setBoundary(const QByteArray & s) {
1764 setParameter(QStringLiteral("boundary"), QString::fromLatin1(s));
1765 }
1766
name() const1767 QString ContentType::name() const {
1768 return parameter(QStringLiteral("name"));
1769 }
1770
setName(const QString & s,const QByteArray & cs)1771 void ContentType::setName(const QString & s, const QByteArray & cs) {
1772 Q_D(ContentType);
1773 d->encCS = cs;
1774 setParameter(QStringLiteral("name"), s);
1775 }
1776
id() const1777 QByteArray ContentType::id() const {
1778 return parameter(QStringLiteral("id")).toLatin1();
1779 }
1780
setId(const QByteArray & s)1781 void ContentType::setId(const QByteArray & s) {
1782 setParameter(QStringLiteral("id"), QString::fromLatin1(s));
1783 }
1784
partialNumber() const1785 int ContentType::partialNumber() const {
1786 QByteArray p = parameter(QStringLiteral("number")).toLatin1();
1787 if (!p.isEmpty()) {
1788 return p.toInt();
1789 } else {
1790 return -1;
1791 }
1792 }
1793
partialCount() const1794 int ContentType::partialCount() const {
1795 QByteArray p = parameter(QStringLiteral("total")).toLatin1();
1796 if (!p.isEmpty()) {
1797 return p.toInt();
1798 } else {
1799 return -1;
1800 }
1801 }
1802
category() const1803 contentCategory ContentType::category() const {
1804 return d_func()->category;
1805 }
1806
setCategory(contentCategory c)1807 void ContentType::setCategory(contentCategory c) {
1808 Q_D(ContentType);
1809 d->category = c;
1810 }
1811
setPartialParams(int total,int number)1812 void ContentType::setPartialParams(int total, int number) {
1813 setParameter(QStringLiteral("number"), QString::number(number));
1814 setParameter(QStringLiteral("total"), QString::number(total));
1815 }
1816
parse(const char * & scursor,const char * const send,bool isCRLF)1817 bool ContentType::parse(const char *&scursor, const char *const send,
1818 bool isCRLF) {
1819 Q_D(ContentType);
1820 // content-type: type "/" subtype *(";" parameter)
1821 clear();
1822 eatCFWS(scursor, send, isCRLF);
1823 if (scursor == send) {
1824 return false; // empty header
1825 }
1826
1827 // type
1828 QPair<const char *, int> maybeMimeType;
1829 if (!parseToken(scursor, send, maybeMimeType, ParseTokenNoFlag)) {
1830 return false;
1831 }
1832 // subtype
1833 eatCFWS(scursor, send, isCRLF);
1834 if (scursor == send || *scursor != '/') {
1835 return false;
1836 }
1837 scursor++;
1838 eatCFWS(scursor, send, isCRLF);
1839 if (scursor == send) {
1840 return false;
1841 }
1842 QPair<const char *, int> maybeSubType;
1843 if (!parseToken(scursor, send, maybeSubType, ParseTokenNoFlag)) {
1844 return false;
1845 }
1846
1847 d->mimeType.reserve(maybeMimeType.second + maybeSubType.second + 1);
1848 d->mimeType = QByteArray(maybeMimeType.first, maybeMimeType.second).toLower()
1849 + '/' + QByteArray(maybeSubType.first, maybeSubType.second).toLower();
1850
1851 // parameter list
1852 eatCFWS(scursor, send, isCRLF);
1853 if (scursor == send) {
1854 goto success; // no parameters
1855 }
1856 if (*scursor != ';') {
1857 return false;
1858 }
1859 scursor++;
1860
1861 if (!Parametrized::parse(scursor, send, isCRLF)) {
1862 return false;
1863 }
1864
1865 // adjust category
1866 success:
1867 if (isMultipart()) {
1868 d->category = CCcontainer;
1869 } else {
1870 d->category = CCsingle;
1871 }
1872 return true;
1873 }
1874
1875 //-----</Content-Type>-------------------------
1876
1877 //-----<ContentID>----------------------
1878
1879 kmime_mk_trivial_ctor_with_name_and_dptr(ContentID, SingleIdent, Content-ID)
kmime_mk_dptr_ctor(ContentID,SingleIdent)1880 kmime_mk_dptr_ctor(ContentID, SingleIdent)
1881
1882 bool ContentID::parse(const char *&scursor, const char *const send, bool isCRLF) {
1883 Q_D(ContentID);
1884 // Content-id := "<" contentid ">"
1885 // contentid := now whitespaces
1886
1887 const char *origscursor = scursor;
1888 if (!SingleIdent::parse(scursor, send, isCRLF)) {
1889 scursor = origscursor;
1890 d->msgIdList.clear();
1891 d->cachedIdentifier.clear();
1892
1893 while (scursor != send) {
1894 eatCFWS(scursor, send, isCRLF);
1895 // empty entry ending the list: OK.
1896 if (scursor == send) {
1897 return true;
1898 }
1899 // empty entry: ignore.
1900 if (*scursor == ',') {
1901 scursor++;
1902 continue;
1903 }
1904
1905 AddrSpec maybeContentId;
1906 // Almost parseAngleAddr
1907 if (scursor == send || *scursor != '<') {
1908 return false;
1909 }
1910 scursor++; // eat '<'
1911
1912 eatCFWS(scursor, send, isCRLF);
1913 if (scursor == send) {
1914 return false;
1915 }
1916
1917 // Save chars until '>''
1918 QByteArray result;
1919 if (!parseDotAtom(scursor, send, result, false)) {
1920 return false;
1921 }
1922
1923 eatCFWS(scursor, send, isCRLF);
1924 if (scursor == send || *scursor != '>') {
1925 return false;
1926 }
1927 scursor++;
1928 // /Almost parseAngleAddr
1929
1930 maybeContentId.localPart = QString::fromLatin1(result); // FIXME: just use QByteArray instead of AddrSpec in msgIdList?
1931 d->msgIdList.append(maybeContentId);
1932
1933 eatCFWS(scursor, send, isCRLF);
1934 // header end ending the list: OK.
1935 if (scursor == send) {
1936 return true;
1937 }
1938 // regular item separator: eat it.
1939 if (*scursor == ',') {
1940 scursor++;
1941 }
1942 }
1943 return true;
1944 } else {
1945 return true;
1946 }
1947 }
1948
1949 //-----</ContentID>----------------------
1950
1951 //-----<ContentTransferEncoding>----------------------------
1952
1953 //@cond PRIVATE
1954 kmime_mk_trivial_ctor_with_name_and_dptr(ContentTransferEncoding,
1955 Generics::Token, Content-Transfer-Encoding)
1956 //@endcond
1957
1958 typedef struct {
1959 const char *s;
1960 int e;
1961 } encTableType;
1962
1963 static const encTableType encTable[] = {
1964 { "7Bit", CE7Bit },
1965 { "8Bit", CE8Bit },
1966 { "quoted-printable", CEquPr },
1967 { "base64", CEbase64 },
1968 { "x-uuencode", CEuuenc },
1969 { "binary", CEbinary },
1970 { nullptr, 0}
1971 };
1972
clear()1973 void ContentTransferEncoding::clear() {
1974 Q_D(ContentTransferEncoding);
1975 d->decoded = true;
1976 d->cte = CE7Bit;
1977 Token::clear();
1978 }
1979
encoding() const1980 contentEncoding ContentTransferEncoding::encoding() const {
1981 return d_func()->cte;
1982 }
1983
setEncoding(contentEncoding e)1984 void ContentTransferEncoding::setEncoding(contentEncoding e) {
1985 Q_D(ContentTransferEncoding);
1986 d->cte = e;
1987
1988 for (int i = 0; encTable[i].s != nullptr; ++i) {
1989 if (d->cte == encTable[i].e) {
1990 setToken(encTable[i].s);
1991 break;
1992 }
1993 }
1994 }
1995
isDecoded() const1996 bool ContentTransferEncoding::isDecoded() const {
1997 return d_func()->decoded;
1998 }
1999
setDecoded(bool decoded)2000 void ContentTransferEncoding::setDecoded(bool decoded) {
2001 Q_D(ContentTransferEncoding);
2002 d->decoded = decoded;
2003 }
2004
needToEncode() const2005 bool ContentTransferEncoding::needToEncode() const {
2006 const Q_D(ContentTransferEncoding);
2007 return d->decoded && (d->cte == CEquPr || d->cte == CEbase64);
2008 }
2009
parse(const char * & scursor,const char * const send,bool isCRLF)2010 bool ContentTransferEncoding::parse(const char *&scursor,
2011 const char *const send, bool isCRLF) {
2012 Q_D(ContentTransferEncoding);
2013 clear();
2014 if (!Token::parse(scursor, send, isCRLF)) {
2015 return false;
2016 }
2017
2018 // TODO: error handling in case of an unknown encoding?
2019 for (int i = 0; encTable[i].s != nullptr; ++i) {
2020 if (qstricmp(token().constData(), encTable[i].s) == 0) {
2021 d->cte = (contentEncoding)encTable[i].e;
2022 break;
2023 }
2024 }
2025 d->decoded = (d->cte == CE7Bit || d->cte == CE8Bit);
2026 return true;
2027 }
2028
2029 //-----</ContentTransferEncoding>---------------------------
2030
2031 //-----<ContentDisposition>--------------------------
2032
2033 //@cond PRIVATE
2034 kmime_mk_trivial_ctor_with_name_and_dptr(ContentDisposition,
2035 Generics::Parametrized, Content-Disposition)
2036 //@endcond
2037
as7BitString(bool withHeaderType) const2038 QByteArray ContentDisposition::as7BitString(bool withHeaderType) const {
2039 if (isEmpty()) {
2040 return QByteArray();
2041 }
2042
2043 QByteArray rv;
2044 if (withHeaderType) {
2045 rv += typeIntro();
2046 }
2047
2048 if (d_func()->disposition == CDattachment) {
2049 rv += "attachment";
2050 } else if (d_func()->disposition == CDinline) {
2051 rv += "inline";
2052 } else {
2053 return QByteArray();
2054 }
2055
2056 if (!Parametrized::isEmpty()) {
2057 rv += "; " + Parametrized::as7BitString(false);
2058 }
2059
2060 return rv;
2061 }
2062
isEmpty() const2063 bool ContentDisposition::isEmpty() const {
2064 return d_func()->disposition == CDInvalid;
2065 }
2066
clear()2067 void ContentDisposition::clear() {
2068 Q_D(ContentDisposition);
2069 d->disposition = CDInvalid;
2070 Parametrized::clear();
2071 }
2072
disposition() const2073 contentDisposition ContentDisposition::disposition() const {
2074 return d_func()->disposition;
2075 }
2076
setDisposition(contentDisposition disp)2077 void ContentDisposition::setDisposition(contentDisposition disp) {
2078 Q_D(ContentDisposition);
2079 d->disposition = disp;
2080 }
2081
filename() const2082 QString KMime::Headers::ContentDisposition::filename() const {
2083 return parameter(QStringLiteral("filename"));
2084 }
2085
setFilename(const QString & filename)2086 void ContentDisposition::setFilename(const QString & filename) {
2087 setParameter(QStringLiteral("filename"), filename);
2088 }
2089
parse(const char * & scursor,const char * const send,bool isCRLF)2090 bool ContentDisposition::parse(const char *&scursor, const char *const send,
2091 bool isCRLF) {
2092 Q_D(ContentDisposition);
2093 clear();
2094
2095 // token
2096 QByteArray token;
2097 eatCFWS(scursor, send, isCRLF);
2098 if (scursor == send) {
2099 return false;
2100 }
2101
2102 QPair<const char *, int> maybeToken;
2103 if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) {
2104 return false;
2105 }
2106
2107 token = QByteArray(maybeToken.first, maybeToken.second).toLower();
2108 if (token == "inline") {
2109 d->disposition = CDinline;
2110 } else if (token == "attachment") {
2111 d->disposition = CDattachment;
2112 } else {
2113 return false;
2114 }
2115
2116 // parameter list
2117 eatCFWS(scursor, send, isCRLF);
2118 if (scursor == send) {
2119 return true; // no parameters
2120 }
2121
2122 if (*scursor != ';') {
2123 return false;
2124 }
2125 scursor++;
2126
2127 return Parametrized::parse(scursor, send, isCRLF);
2128 }
2129
2130 //-----</ContentDisposition>-------------------------
2131
2132 //@cond PRIVATE
kmime_mk_trivial_ctor_with_name(Subject,Generics::Unstructured,Subject)2133 kmime_mk_trivial_ctor_with_name(Subject, Generics::Unstructured, Subject)
2134 //@endcond
2135
2136 Base *createHeader(const QByteArray & type) {
2137 return HeaderFactory::createHeader(type);
2138 }
2139
2140 //@cond PRIVATE
2141 kmime_mk_trivial_ctor_with_name(ContentDescription,
2142 Generics::Unstructured, Content-Description)
2143 kmime_mk_trivial_ctor_with_name(ContentLocation,
2144 Generics::Unstructured, Content-Location)
2145 kmime_mk_trivial_ctor_with_name(From, Generics::MailboxList, From)
2146 kmime_mk_trivial_ctor_with_name(Sender, Generics::SingleMailbox, Sender)
2147 kmime_mk_trivial_ctor_with_name(To, Generics::AddressList, To)
2148 kmime_mk_trivial_ctor_with_name(Cc, Generics::AddressList, Cc)
2149 kmime_mk_trivial_ctor_with_name(Bcc, Generics::AddressList, Bcc)
2150 kmime_mk_trivial_ctor_with_name(ReplyTo, Generics::AddressList, Reply-To)
2151 kmime_mk_trivial_ctor_with_name(Keywords, Generics::PhraseList, Keywords)
2152 kmime_mk_trivial_ctor_with_name(MIMEVersion, Generics::DotAtom, MIME-Version)
2153 kmime_mk_trivial_ctor_with_name(Supersedes, Generics::SingleIdent, Supersedes)
2154 kmime_mk_trivial_ctor_with_name(InReplyTo, Generics::Ident, In-Reply-To)
2155 kmime_mk_trivial_ctor_with_name(References, Generics::Ident, References)
2156 kmime_mk_trivial_ctor_with_name(Organization, Generics::Unstructured, Organization)
2157 kmime_mk_trivial_ctor_with_name(UserAgent, Generics::Unstructured, User-Agent)
2158 //@endcond
2159
2160 } // namespace Headers
2161
2162 } // namespace KMime
2163