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