KMime

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

KDE's Doxygen guidelines are available online.