• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.4 API Reference
  • KDE Home
  • Contact Us
 

KMIME Library

  • kmime
kmime_header_parsing.cpp
1 /* -*- c++ -*-
2  kmime_header_parsing.cpp
3 
4  KMime, the KDE Internet mail/usenet news message library.
5  Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
6 
7  This library is free software; you can redistribute it and/or
8  modify it under the terms of the GNU Library General Public
9  License as published by the Free Software Foundation; either
10  version 2 of the License, or (at your option) any later version.
11 
12  This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Library General Public License for more details.
16 
17  You should have received a copy of the GNU Library General Public License
18  along with this library; see the file COPYING.LIB. If not, write to
19  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  Boston, MA 02110-1301, USA.
21 */
22 
23 #include "kmime_header_parsing.h"
24 
25 #include "kmime_codecs.h"
26 #include "kmime_headerfactory_p.h"
27 #include "kmime_headers.h"
28 #include "kmime_util.h"
29 #include "kmime_util_p.h"
30 #include "kmime_dateformatter.h"
31 #include "kmime_warning.h"
32 
33 #include <kglobal.h>
34 #include <kcharsets.h>
35 
36 #include <QtCore/QTextCodec>
37 #include <QtCore/QMap>
38 #include <QtCore/QStringList>
39 #include <QtCore/QUrl>
40 
41 #include <ctype.h> // for isdigit
42 #include <cassert>
43 
44 using namespace KMime;
45 using namespace KMime::Types;
46 
47 namespace KMime {
48 
49 namespace Types {
50 
51 // QUrl::fromAce is extremely expensive, so only use it when necessary.
52 // Fortunately, the presence of IDNA is readily detected with a substring match...
53 static inline QString QUrl_fromAce_wrapper( const QString & domain )
54 {
55  if ( domain.contains( QLatin1String( "xn--" ) ) )
56  return QUrl::fromAce( domain.toLatin1() );
57  else
58  return domain;
59 }
60 
61 static QString addr_spec_as_string( const AddrSpec & as, bool pretty )
62 {
63  if ( as.isEmpty() ) {
64  return QString();
65  }
66 
67  static QChar dotChar = QLatin1Char( '.' );
68  static QChar backslashChar = QLatin1Char( '\\' );
69  static QChar quoteChar = QLatin1Char( '"' );
70 
71  bool needsQuotes = false;
72  QString result;
73  result.reserve( as.localPart.length() + as.domain.length() + 1 );
74  for ( int i = 0 ; i < as.localPart.length() ; ++i ) {
75  const QChar ch = as.localPart.at( i );
76  if ( ch == dotChar || isAText( ch.toLatin1() ) ) {
77  result += ch;
78  } else {
79  needsQuotes = true;
80  if ( ch == backslashChar || ch == quoteChar ) {
81  result += backslashChar;
82  }
83  result += ch;
84  }
85  }
86  const QString dom = pretty ? QUrl_fromAce_wrapper( as.domain ) : as.domain ;
87  if ( needsQuotes ) {
88  result = quoteChar + result + quoteChar;
89  }
90  if( dom.isEmpty() ) {
91  return result;
92  } else {
93  result += QLatin1Char( '@' );
94  result += dom;
95  return result;
96  }
97 }
98 
99 QString AddrSpec::asString() const
100 {
101  return addr_spec_as_string( *this, false );
102 }
103 
104 QString AddrSpec::asPrettyString() const
105 {
106  return addr_spec_as_string( *this, true );
107 }
108 
109 bool AddrSpec::isEmpty() const
110 {
111  return localPart.isEmpty() && domain.isEmpty();
112 }
113 
114 QByteArray Mailbox::address() const
115 {
116  QByteArray result;
117  const QString asString = addr_spec_as_string( mAddrSpec, false );
118  if ( !asString.isEmpty() ) {
119  result = asString.toLatin1();
120  }
121  return result;
122  //return mAddrSpec.asString().toLatin1();
123 }
124 
125 AddrSpec Mailbox::addrSpec() const
126 {
127  return mAddrSpec;
128 }
129 
130 QString Mailbox::name() const
131 {
132  return mDisplayName;
133 }
134 
135 void Mailbox::setAddress( const AddrSpec &addr )
136 {
137  mAddrSpec = addr;
138 }
139 
140 void Mailbox::setAddress( const QByteArray &addr )
141 {
142  const char *cursor = addr.constData();
143  if ( !HeaderParsing::parseAngleAddr( cursor,
144  cursor + addr.length(), mAddrSpec ) ) {
145  if ( !HeaderParsing::parseAddrSpec( cursor, cursor + addr.length(),
146  mAddrSpec ) ) {
147  kWarning() << "Invalid address";
148  return;
149  }
150  }
151 }
152 
153 void Mailbox::setName( const QString &name )
154 {
155  mDisplayName = removeBidiControlChars( name );
156 }
157 
158 void Mailbox::setNameFrom7Bit( const QByteArray &name,
159  const QByteArray &defaultCharset )
160 {
161  QByteArray cs;
162  setName( decodeRFC2047String( name, cs, defaultCharset, false ) );
163 }
164 
165 bool Mailbox::hasAddress() const
166 {
167  return !mAddrSpec.isEmpty();
168 }
169 
170 bool Mailbox::hasName() const
171 {
172  return !mDisplayName.isEmpty();
173 }
174 
175 QString Mailbox::prettyAddress() const
176 {
177  return prettyAddress( QuoteNever );
178 }
179 
180 QString Mailbox::prettyAddress( Quoting quoting ) const
181 {
182  if ( !hasName() ) {
183  return QLatin1String( address() );
184  }
185  QString s = name();
186  if ( quoting != QuoteNever ) {
187  addQuotes( s, quoting == QuoteAlways /*bool force*/ );
188  }
189 
190  if ( hasAddress() ) {
191  s += QLatin1String(" <") + QLatin1String( address() ) + QLatin1Char('>');
192  }
193  return s;
194 }
195 
196 void Mailbox::fromUnicodeString( const QString &s )
197 {
198  from7BitString( encodeRFC2047Sentence( s, "utf-8" ) );
199 }
200 
201 void Mailbox::from7BitString( const QByteArray &s )
202 {
203  const char *cursor = s.constData();
204  HeaderParsing::parseMailbox( cursor, cursor + s.length(), *this );
205 }
206 
207 QByteArray KMime::Types::Mailbox::as7BitString( const QByteArray &encCharset ) const
208 {
209  if ( !hasName() ) {
210  return address();
211  }
212  QByteArray rv;
213  if ( isUsAscii( name() ) ) {
214  QByteArray tmp = name().toLatin1();
215  addQuotes( tmp, false );
216  rv += tmp;
217  } else {
218  rv += encodeRFC2047String( name(), encCharset, true );
219  }
220  if ( hasAddress() ) {
221  rv += " <" + address() + '>';
222  }
223  return rv;
224 }
225 
226 } // namespace Types
227 
228 namespace HeaderParsing {
229 
230 // parse the encoded-word (scursor points to after the initial '=')
231 bool parseEncodedWord( const char* &scursor, const char * const send,
232  QString &result, QByteArray &language,
233  QByteArray &usedCS, const QByteArray &defaultCS,
234  bool forceCS )
235 {
236  // make sure the caller already did a bit of the work.
237  assert( *(scursor-1) == '=' );
238 
239  //
240  // STEP 1:
241  // scan for the charset/language portion of the encoded-word
242  //
243 
244  char ch = *scursor++;
245 
246  if ( ch != '?' ) {
247  // kDebug() << "first";
248  //KMIME_WARN_PREMATURE_END_OF( EncodedWord );
249  return false;
250  }
251 
252  // remember start of charset (ie. just after the initial "=?") and
253  // language (just after the first '*') fields:
254  const char * charsetStart = scursor;
255  const char * languageStart = 0;
256 
257  // find delimiting '?' (and the '*' separating charset and language
258  // tags, if any):
259  for ( ; scursor != send ; scursor++ ) {
260  if ( *scursor == '?') {
261  break;
262  } else if ( *scursor == '*' && languageStart == 0 ) {
263  languageStart = scursor + 1;
264  }
265  }
266 
267  // not found? can't be an encoded-word!
268  if ( scursor == send || *scursor != '?' ) {
269  // kDebug() << "second";
270  KMIME_WARN_PREMATURE_END_OF( EncodedWord );
271  return false;
272  }
273 
274  // extract the language information, if any (if languageStart is 0,
275  // language will be null, too):
276  QByteArray maybeLanguage( languageStart, scursor - languageStart );
277  // extract charset information (keep in mind: the size given to the
278  // ctor is one off due to the \0 terminator):
279  QByteArray maybeCharset( charsetStart,
280  ( languageStart ? languageStart - 1 : scursor ) - charsetStart );
281 
282  //
283  // STEP 2:
284  // scan for the encoding portion of the encoded-word
285  //
286 
287  // remember start of encoding (just _after_ the second '?'):
288  scursor++;
289  const char * encodingStart = scursor;
290 
291  // find next '?' (ending the encoding tag):
292  for ( ; scursor != send ; scursor++ ) {
293  if ( *scursor == '?' ) {
294  break;
295  }
296  }
297 
298  // not found? Can't be an encoded-word!
299  if ( scursor == send || *scursor != '?' ) {
300  // kDebug() << "third";
301  KMIME_WARN_PREMATURE_END_OF( EncodedWord );
302  return false;
303  }
304 
305  // extract the encoding information:
306  QByteArray maybeEncoding( encodingStart, scursor - encodingStart );
307 
308  // kDebug() << "parseEncodedWord: found charset == \"" << maybeCharset
309  // << "\"; language == \"" << maybeLanguage
310  // << "\"; encoding == \"" << maybeEncoding << "\"";
311 
312  //
313  // STEP 3:
314  // scan for encoded-text portion of encoded-word
315  //
316 
317  // remember start of encoded-text (just after the third '?'):
318  scursor++;
319  const char * encodedTextStart = scursor;
320 
321  // find the '?=' sequence (ending the encoded-text):
322  for ( ; scursor != send ; scursor++ ) {
323  if ( *scursor == '?' ) {
324  if ( scursor + 1 != send ) {
325  if ( *( scursor + 1 ) != '=' ) { // We expect a '=' after the '?', but we got something else; ignore
326  KMIME_WARN << "Stray '?' in q-encoded word, ignoring this.";
327  continue;
328  }
329  else { // yep, found a '?=' sequence
330  scursor += 2;
331  break;
332  }
333  }
334  else { // The '?' is the last char, but we need a '=' after it!
335  KMIME_WARN_PREMATURE_END_OF( EncodedWord );
336  return false;
337  }
338  }
339  }
340 
341  if ( *( scursor - 2 ) != '?' || *( scursor - 1 ) != '=' ||
342  scursor < encodedTextStart + 2 ) {
343  KMIME_WARN_PREMATURE_END_OF( EncodedWord );
344  return false;
345  }
346 
347  // set end sentinel for encoded-text:
348  const char * const encodedTextEnd = scursor - 2;
349 
350  //
351  // STEP 4:
352  // setup decoders for the transfer encoding and the charset
353  //
354 
355  // try if there's a codec for the encoding found:
356  Codec * codec = Codec::codecForName( maybeEncoding );
357  if ( !codec ) {
358  KMIME_WARN_UNKNOWN( Encoding, maybeEncoding );
359  return false;
360  }
361 
362  // get an instance of a corresponding decoder:
363  Decoder * dec = codec->makeDecoder();
364  assert( dec );
365 
366  // try if there's a (text)codec for the charset found:
367  bool matchOK = false;
368  QTextCodec *textCodec = 0;
369  if ( forceCS || maybeCharset.isEmpty() ) {
370  textCodec = KGlobal::charsets()->codecForName( QLatin1String( defaultCS ), matchOK );
371  usedCS = cachedCharset( defaultCS );
372  } else {
373  textCodec = KGlobal::charsets()->codecForName( QLatin1String( maybeCharset ), matchOK );
374  if ( !matchOK ) { //no suitable codec found => use default charset
375  textCodec = KGlobal::charsets()->codecForName( QLatin1String( defaultCS ), matchOK );
376  usedCS = cachedCharset( defaultCS );
377  } else {
378  usedCS = cachedCharset( maybeCharset );
379  }
380  }
381 
382  if ( !matchOK || !textCodec ) {
383  KMIME_WARN_UNKNOWN( Charset, maybeCharset );
384  delete dec;
385  return false;
386  };
387 
388  // kDebug() << "mimeName(): \"" << textCodec->name() << "\"";
389 
390  // allocate a temporary buffer to store the 8bit text:
391  int encodedTextLength = encodedTextEnd - encodedTextStart;
392  QByteArray buffer;
393  buffer.resize( codec->maxDecodedSizeFor( encodedTextLength ) );
394  char *bbegin = buffer.data();
395  char *bend = bbegin + buffer.length();
396 
397  //
398  // STEP 5:
399  // do the actual decoding
400  //
401 
402  if ( !dec->decode( encodedTextStart, encodedTextEnd, bbegin, bend ) ) {
403  KMIME_WARN << codec->name() << "codec lies about its maxDecodedSizeFor("
404  << encodedTextLength << ")\nresult may be truncated";
405  }
406 
407  result = textCodec->toUnicode( buffer.data(), bbegin - buffer.data() );
408 
409  // kDebug() << "result now: \"" << result << "\"";
410  // cleanup:
411  delete dec;
412  language = maybeLanguage;
413 
414  return true;
415 }
416 
417 static inline void eatWhiteSpace( const char* &scursor, const char * const send )
418 {
419  while ( scursor != send &&
420  ( *scursor == ' ' || *scursor == '\n' ||
421  *scursor == '\t' || *scursor == '\r' ) )
422  scursor++;
423 }
424 
425 bool parseAtom( const char * &scursor, const char * const send,
426  QString &result, bool allow8Bit )
427 {
428  QPair<const char*,int> maybeResult;
429 
430  if ( parseAtom( scursor, send, maybeResult, allow8Bit ) ) {
431  result += QString::fromLatin1( maybeResult.first, maybeResult.second );
432  return true;
433  }
434 
435  return false;
436 }
437 
438 bool parseAtom( const char * &scursor, const char * const send,
439  QPair<const char*,int> &result, bool allow8Bit )
440 {
441  bool success = false;
442  const char *start = scursor;
443 
444  while ( scursor != send ) {
445  signed char ch = *scursor++;
446  if ( ch > 0 && isAText( ch ) ) {
447  // AText: OK
448  success = true;
449  } else if ( allow8Bit && ch < 0 ) {
450  // 8bit char: not OK, but be tolerant.
451  KMIME_WARN_8BIT( ch );
452  success = true;
453  } else {
454  // CTL or special - marking the end of the atom:
455  // re-set sursor to point to the offending
456  // char and return:
457  scursor--;
458  break;
459  }
460  }
461  result.first = start;
462  result.second = scursor - start;
463  return success;
464 }
465 
466 // FIXME: Remove this and the other parseToken() method. add a new one where "result" is a
467 // QByteArray.
468 bool parseToken( const char * &scursor, const char * const send,
469  QString &result, bool allow8Bit )
470 {
471  QPair<const char*,int> maybeResult;
472 
473  if ( parseToken( scursor, send, maybeResult, allow8Bit ) ) {
474  result += QString::fromLatin1( maybeResult.first, maybeResult.second );
475  return true;
476  }
477 
478  return false;
479 }
480 
481 bool parseToken( const char * &scursor, const char * const send,
482  QPair<const char*,int> &result, bool allow8Bit )
483 {
484  bool success = false;
485  const char * start = scursor;
486 
487  while ( scursor != send ) {
488  signed char ch = *scursor++;
489  if ( ch > 0 && isTText( ch ) ) {
490  // TText: OK
491  success = true;
492  } else if ( allow8Bit && ch < 0 ) {
493  // 8bit char: not OK, but be tolerant.
494  KMIME_WARN_8BIT( ch );
495  success = true;
496  } else {
497  // CTL or tspecial - marking the end of the atom:
498  // re-set sursor to point to the offending
499  // char and return:
500  scursor--;
501  break;
502  }
503  }
504  result.first = start;
505  result.second = scursor - start;
506  return success;
507 }
508 
509 #define READ_ch_OR_FAIL if ( scursor == send ) { \
510  KMIME_WARN_PREMATURE_END_OF( GenericQuotedString ); \
511  return false; \
512  } else { \
513  ch = *scursor++; \
514  }
515 
516 // known issues:
517 //
518 // - doesn't handle quoted CRLF
519 
520 // FIXME: Why is result a QString? This should be a QByteArray, since at this level, we don't
521 // know about encodings yet!
522 bool parseGenericQuotedString( const char* &scursor, const char * const send,
523  QString &result, bool isCRLF,
524  const char openChar, const char closeChar )
525 {
526  char ch;
527  // We are in a quoted-string or domain-literal or comment and the
528  // cursor points to the first char after the openChar.
529  // We will apply unfolding and quoted-pair removal.
530  // We return when we either encounter the end or unescaped openChar
531  // or closeChar.
532 
533  assert( *(scursor-1) == openChar || *(scursor-1) == closeChar );
534 
535  while ( scursor != send ) {
536  ch = *scursor++;
537 
538  if ( ch == closeChar || ch == openChar ) {
539  // end of quoted-string or another opening char:
540  // let caller decide what to do.
541  return true;
542  }
543 
544  switch( ch ) {
545  case '\\': // quoted-pair
546  // misses "\" CRLF LWSP-char handling, see rfc822, 3.4.5
547  READ_ch_OR_FAIL;
548  KMIME_WARN_IF_8BIT( ch );
549  result += QLatin1Char( ch );
550  break;
551  case '\r':
552  // ###
553  // The case of lonely '\r' is easy to solve, as they're
554  // not part of Unix Line-ending conventions.
555  // But I see a problem if we are given Unix-native
556  // line-ending-mails, where we cannot determine anymore
557  // whether a given '\n' was part of a CRLF or was occurring
558  // on it's own.
559  READ_ch_OR_FAIL;
560  if ( ch != '\n' ) {
561  // CR on it's own...
562  KMIME_WARN_LONE( CR );
563  result += QLatin1Char('\r');
564  scursor--; // points to after the '\r' again
565  } else {
566  // CRLF encountered.
567  // lookahead: check for folding
568  READ_ch_OR_FAIL;
569  if ( ch == ' ' || ch == '\t' ) {
570  // correct folding;
571  // position cursor behind the CRLF WSP (unfolding)
572  // and add the WSP to the result
573  result += QLatin1Char( ch );
574  } else {
575  // this is the "shouldn't happen"-case. There is a CRLF
576  // inside a quoted-string without it being part of FWS.
577  // We take it verbatim.
578  KMIME_WARN_NON_FOLDING( CRLF );
579  result += QLatin1String( "\r\n" );
580  // the cursor is decremented again, so's we need not
581  // duplicate the whole switch here. "ch" could've been
582  // everything (incl. openChar or closeChar).
583  scursor--;
584  }
585  }
586  break;
587  case '\n':
588  // Note: CRLF has been handled above already!
589  // ### LF needs special treatment, depending on whether isCRLF
590  // is true (we can be sure a lonely '\n' was meant this way) or
591  // false ('\n' alone could have meant LF or CRLF in the original
592  // message. This parser assumes CRLF iff the LF is followed by
593  // either WSP (folding) or NULL (premature end of quoted-string;
594  // Should be fixed, since NULL is allowed as per rfc822).
595  READ_ch_OR_FAIL;
596  if ( !isCRLF && ( ch == ' ' || ch == '\t' ) ) {
597  // folding
598  // correct folding
599  result += QLatin1Char( ch );
600  } else {
601  // non-folding
602  KMIME_WARN_LONE( LF );
603  result += QLatin1Char( '\n' );
604  // pos is decremented, so's we need not duplicate the whole
605  // switch here. ch could've been everything (incl. <">, "\").
606  scursor--;
607  }
608  break;
609  case '=':
610  {
611  // ### Work around broken clients that send encoded words in quoted-strings
612  // For example, older KMail versions.
613  if( scursor == send )
614  break;
615 
616  const char *oldscursor = scursor;
617  QString tmp;
618  QByteArray lang, charset;
619  if( *scursor++ == '?' ) {
620  --scursor;
621  if( parseEncodedWord( scursor, send, tmp, lang, charset ) ) {
622  result += tmp;
623  break;
624  } else {
625  scursor = oldscursor;
626  }
627  } else {
628  scursor = oldscursor;
629  }
630  // fall through
631  }
632  default:
633  KMIME_WARN_IF_8BIT( ch );
634  result += QLatin1Char( ch );
635  }
636  }
637 
638  return false;
639 }
640 
641 // known issues:
642 //
643 // - doesn't handle encoded-word inside comments.
644 
645 bool parseComment( const char* &scursor, const char * const send,
646  QString &result, bool isCRLF, bool reallySave )
647 {
648  int commentNestingDepth = 1;
649  const char *afterLastClosingParenPos = 0;
650  QString maybeCmnt;
651  const char *oldscursor = scursor;
652 
653  assert( *(scursor-1) == '(' );
654 
655  while ( commentNestingDepth ) {
656  QString cmntPart;
657  if ( parseGenericQuotedString( scursor, send, cmntPart, isCRLF, '(', ')' ) ) {
658  assert( *(scursor-1) == ')' || *(scursor-1) == '(' );
659  // see the kdoc for above function for the possible conditions
660  // we have to check:
661  switch ( *(scursor-1) ) {
662  case ')':
663  if ( reallySave ) {
664  // add the chunk that's now surely inside the comment.
665  result += maybeCmnt;
666  result += cmntPart;
667  if ( commentNestingDepth > 1 ) {
668  // don't add the outermost ')'...
669  result += QLatin1Char( ')' );
670  }
671  maybeCmnt.clear();
672  }
673  afterLastClosingParenPos = scursor;
674  --commentNestingDepth;
675  break;
676  case '(':
677  if ( reallySave ) {
678  // don't add to "result" yet, because we might find that we
679  // are already outside the (broken) comment...
680  maybeCmnt += cmntPart;
681  maybeCmnt += QLatin1Char( '(' );
682  }
683  ++commentNestingDepth;
684  break;
685  default: assert( 0 );
686  } // switch
687  } else {
688  // !parseGenericQuotedString, ie. premature end
689  if ( afterLastClosingParenPos ) {
690  scursor = afterLastClosingParenPos;
691  } else {
692  scursor = oldscursor;
693  }
694  return false;
695  }
696  } // while
697 
698  return true;
699 }
700 
701 // known issues: none.
702 
703 bool parsePhrase( const char* &scursor, const char * const send,
704  QString &result, bool isCRLF )
705 {
706  enum {
707  None, Phrase, Atom, EncodedWord, QuotedString
708  } found = None;
709 
710  QString tmp;
711  QByteArray lang, charset;
712  const char *successfullyParsed = 0;
713  // only used by the encoded-word branch
714  const char *oldscursor;
715  // used to suppress whitespace between adjacent encoded-words
716  // (rfc2047, 6.2):
717  bool lastWasEncodedWord = false;
718 
719  while ( scursor != send ) {
720  char ch = *scursor++;
721  switch ( ch ) {
722  case '.': // broken, but allow for intorop's sake
723  if ( found == None ) {
724  --scursor;
725  return false;
726  } else {
727  if ( scursor != send && ( *scursor == ' ' || *scursor == '\t' ) ) {
728  result += QLatin1String( ". " );
729  } else {
730  result += QLatin1Char( '.' );
731  }
732  successfullyParsed = scursor;
733  }
734  break;
735  case '"': // quoted-string
736  tmp.clear();
737  if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) {
738  successfullyParsed = scursor;
739  assert( *(scursor-1) == '"' );
740  switch ( found ) {
741  case None:
742  found = QuotedString;
743  break;
744  case Phrase:
745  case Atom:
746  case EncodedWord:
747  case QuotedString:
748  found = Phrase;
749  result += QLatin1Char(' '); // rfc822, 3.4.4
750  break;
751  default:
752  assert( 0 );
753  }
754  lastWasEncodedWord = false;
755  result += tmp;
756  } else {
757  // premature end of quoted string.
758  // What to do? Return leading '"' as special? Return as quoted-string?
759  // We do the latter if we already found something, else signal failure.
760  if ( found == None ) {
761  return false;
762  } else {
763  result += QLatin1Char(' '); // rfc822, 3.4.4
764  result += tmp;
765  return true;
766  }
767  }
768  break;
769  case '(': // comment
770  // parse it, but ignore content:
771  tmp.clear();
772  if ( parseComment( scursor, send, tmp, isCRLF,
773  false /*don't bother with the content*/ ) ) {
774  successfullyParsed = scursor;
775  lastWasEncodedWord = false; // strictly interpreting rfc2047, 6.2
776  } else {
777  if ( found == None ) {
778  return false;
779  } else {
780  scursor = successfullyParsed;
781  return true;
782  }
783  }
784  break;
785  case '=': // encoded-word
786  tmp.clear();
787  oldscursor = scursor;
788  lang.clear();
789  charset.clear();
790  if ( parseEncodedWord( scursor, send, tmp, lang, charset ) ) {
791  successfullyParsed = scursor;
792  switch ( found ) {
793  case None:
794  found = EncodedWord;
795  break;
796  case Phrase:
797  case EncodedWord:
798  case Atom:
799  case QuotedString:
800  if ( !lastWasEncodedWord ) {
801  result += QLatin1Char(' '); // rfc822, 3.4.4
802  }
803  found = Phrase;
804  break;
805  default: assert( 0 );
806  }
807  lastWasEncodedWord = true;
808  result += tmp;
809  break;
810  } else {
811  // parse as atom:
812  scursor = oldscursor;
813  }
814  // fall though...
815 
816  default: //atom
817  tmp.clear();
818  scursor--;
819  if ( parseAtom( scursor, send, tmp, true /* allow 8bit */ ) ) {
820  successfullyParsed = scursor;
821  switch ( found ) {
822  case None:
823  found = Atom;
824  break;
825  case Phrase:
826  case Atom:
827  case EncodedWord:
828  case QuotedString:
829  found = Phrase;
830  result += QLatin1Char(' '); // rfc822, 3.4.4
831  break;
832  default:
833  assert( 0 );
834  }
835  lastWasEncodedWord = false;
836  result += tmp;
837  } else {
838  if ( found == None ) {
839  return false;
840  } else {
841  scursor = successfullyParsed;
842  return true;
843  }
844  }
845  }
846  eatWhiteSpace( scursor, send );
847  }
848 
849  return found != None;
850 }
851 
852 // FIXME: This should probably by QByteArray &result instead?
853 bool parseDotAtom( const char* &scursor, const char * const send,
854  QString &result, bool isCRLF )
855 {
856  eatCFWS( scursor, send, isCRLF );
857 
858  // always points to just after the last atom parsed:
859  const char *successfullyParsed;
860 
861  QString tmp;
862  if ( !parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) {
863  return false;
864  }
865  result += tmp;
866  successfullyParsed = scursor;
867 
868  while ( scursor != send ) {
869 
870  // end of header or no '.' -> return
871  if ( scursor == send || *scursor != '.' ) {
872  return true;
873  }
874  scursor++; // eat '.'
875 
876  if ( scursor == send || !isAText( *scursor ) ) {
877  // end of header or no AText, but this time following a '.'!:
878  // reset cursor to just after last successfully parsed char and
879  // return:
880  scursor = successfullyParsed;
881  return true;
882  }
883 
884  // try to parse the next atom:
885  QString maybeAtom;
886  if ( !parseAtom( scursor, send, maybeAtom, false /*no 8bit*/ ) ) {
887  scursor = successfullyParsed;
888  return true;
889  }
890 
891  result += QLatin1Char('.');
892  result += maybeAtom;
893  successfullyParsed = scursor;
894  }
895 
896  scursor = successfullyParsed;
897  return true;
898 }
899 
900 void eatCFWS( const char* &scursor, const char * const send, bool isCRLF )
901 {
902  QString dummy;
903 
904  while ( scursor != send ) {
905  const char *oldscursor = scursor;
906 
907  char ch = *scursor++;
908 
909  switch( ch ) {
910  case ' ':
911  case '\t': // whitespace
912  case '\r':
913  case '\n': // folding
914  continue;
915 
916  case '(': // comment
917  if ( parseComment( scursor, send, dummy, isCRLF, false /*don't save*/ ) ) {
918  continue;
919  }
920  scursor = oldscursor;
921  return;
922 
923  default:
924  scursor = oldscursor;
925  return;
926  }
927  }
928 }
929 
930 bool parseDomain( const char* &scursor, const char * const send,
931  QString &result, bool isCRLF )
932 {
933  eatCFWS( scursor, send, isCRLF );
934  if ( scursor == send ) {
935  return false;
936  }
937 
938  // domain := dot-atom / domain-literal / atom *("." atom)
939  //
940  // equivalent to:
941  // domain = dot-atom / domain-literal,
942  // since parseDotAtom does allow CFWS between atoms and dots
943 
944  if ( *scursor == '[' ) {
945  // domain-literal:
946  QString maybeDomainLiteral;
947  // eat '[':
948  scursor++;
949  while ( parseGenericQuotedString( scursor, send, maybeDomainLiteral,
950  isCRLF, '[', ']' ) ) {
951  if ( scursor == send ) {
952  // end of header: check for closing ']':
953  if ( *(scursor-1) == ']' ) {
954  // OK, last char was ']':
955  result = maybeDomainLiteral;
956  return true;
957  } else {
958  // not OK, domain-literal wasn't closed:
959  return false;
960  }
961  }
962  // we hit openChar in parseGenericQuotedString.
963  // include it in maybeDomainLiteral and keep on parsing:
964  if ( *(scursor-1) == '[' ) {
965  maybeDomainLiteral += QLatin1Char('[');
966  continue;
967  }
968  // OK, real end of domain-literal:
969  result = maybeDomainLiteral;
970  return true;
971  }
972  } else {
973  // dot-atom:
974  QString maybeDotAtom;
975  if ( parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) {
976  result = maybeDotAtom;
977  // Domain may end with '.', if so preserve it'
978  if ( scursor != send && *scursor == '.' ) {
979  result += QLatin1Char('.');
980  scursor++;
981  }
982  return true;
983  }
984  }
985  return false;
986 }
987 
988 bool parseObsRoute( const char* &scursor, const char* const send,
989  QStringList &result, bool isCRLF, bool save )
990 {
991  while ( scursor != send ) {
992  eatCFWS( scursor, send, isCRLF );
993  if ( scursor == send ) {
994  return false;
995  }
996 
997  // empty entry:
998  if ( *scursor == ',' ) {
999  scursor++;
1000  if ( save ) {
1001  result.append( QString() );
1002  }
1003  continue;
1004  }
1005 
1006  // empty entry ending the list:
1007  if ( *scursor == ':' ) {
1008  scursor++;
1009  if ( save ) {
1010  result.append( QString() );
1011  }
1012  return true;
1013  }
1014 
1015  // each non-empty entry must begin with '@':
1016  if ( *scursor != '@' ) {
1017  return false;
1018  } else {
1019  scursor++;
1020  }
1021 
1022  QString maybeDomain;
1023  if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) {
1024  return false;
1025  }
1026  if ( save ) {
1027  result.append( maybeDomain );
1028  }
1029 
1030  // eat the following (optional) comma:
1031  eatCFWS( scursor, send, isCRLF );
1032  if ( scursor == send ) {
1033  return false;
1034  }
1035  if ( *scursor == ':' ) {
1036  scursor++;
1037  return true;
1038  }
1039  if ( *scursor == ',' ) {
1040  scursor++;
1041  }
1042  }
1043 
1044  return false;
1045 }
1046 
1047 bool parseAddrSpec( const char* &scursor, const char * const send,
1048  AddrSpec &result, bool isCRLF )
1049 {
1050  //
1051  // STEP 1:
1052  // local-part := dot-atom / quoted-string / word *("." word)
1053  //
1054  // this is equivalent to:
1055  // local-part := word *("." word)
1056 
1057  QString maybeLocalPart;
1058  QString tmp;
1059 
1060  while ( scursor != send ) {
1061  // first, eat any whitespace
1062  eatCFWS( scursor, send, isCRLF );
1063 
1064  char ch = *scursor++;
1065  switch ( ch ) {
1066  case '.': // dot
1067  maybeLocalPart += QLatin1Char('.');
1068  break;
1069 
1070  case '@':
1071  goto SAW_AT_SIGN;
1072  break;
1073 
1074  case '"': // quoted-string
1075  tmp.clear();
1076  if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) {
1077  maybeLocalPart += tmp;
1078  } else {
1079  return false;
1080  }
1081  break;
1082 
1083  default: // atom
1084  scursor--; // re-set scursor to point to ch again
1085  tmp.clear();
1086  if ( parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) {
1087  maybeLocalPart += tmp;
1088  } else {
1089  return false; // parseAtom can only fail if the first char is non-atext.
1090  }
1091  break;
1092  }
1093  }
1094 
1095  return false;
1096 
1097  //
1098  // STEP 2:
1099  // domain
1100  //
1101 
1102 SAW_AT_SIGN:
1103 
1104  assert( *(scursor-1) == '@' );
1105 
1106  QString maybeDomain;
1107  if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) {
1108  return false;
1109  }
1110 
1111  result.localPart = maybeLocalPart;
1112  result.domain = maybeDomain;
1113 
1114  return true;
1115 }
1116 
1117 bool parseAngleAddr( const char* &scursor, const char * const send,
1118  AddrSpec &result, bool isCRLF )
1119 {
1120  // first, we need an opening angle bracket:
1121  eatCFWS( scursor, send, isCRLF );
1122  if ( scursor == send || *scursor != '<' ) {
1123  return false;
1124  }
1125  scursor++; // eat '<'
1126 
1127  eatCFWS( scursor, send, isCRLF );
1128  if ( scursor == send ) {
1129  return false;
1130  }
1131 
1132  if ( *scursor == '@' || *scursor == ',' ) {
1133  // obs-route: parse, but ignore:
1134  KMIME_WARN << "obsolete source route found! ignoring.";
1135  QStringList dummy;
1136  if ( !parseObsRoute( scursor, send, dummy,
1137  isCRLF, false /* don't save */ ) ) {
1138  return false;
1139  }
1140  // angle-addr isn't complete until after the '>':
1141  if ( scursor == send ) {
1142  return false;
1143  }
1144  }
1145 
1146  // parse addr-spec:
1147  AddrSpec maybeAddrSpec;
1148  if ( !parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) {
1149  return false;
1150  }
1151 
1152  eatCFWS( scursor, send, isCRLF );
1153  if ( scursor == send || *scursor != '>' ) {
1154  return false;
1155  }
1156  scursor++;
1157 
1158  result = maybeAddrSpec;
1159  return true;
1160 
1161 }
1162 
1163 static QString stripQuotes( const QString &input )
1164 {
1165  const QLatin1Char quotes( '"' );
1166  if ( input.startsWith( quotes ) && input.endsWith( quotes ) ) {
1167  QString stripped( input.mid( 1, input.size() - 2 ) );
1168  return stripped;
1169  }
1170  else return input;
1171 }
1172 
1173 bool parseMailbox( const char* &scursor, const char * const send,
1174  Mailbox &result, bool isCRLF )
1175 {
1176  eatCFWS( scursor, send, isCRLF );
1177  if ( scursor == send ) {
1178  return false;
1179  }
1180 
1181  AddrSpec maybeAddrSpec;
1182  QString maybeDisplayName;
1183 
1184  // first, try if it's a vanilla addr-spec:
1185  const char * oldscursor = scursor;
1186  if ( parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) {
1187  result.setAddress( maybeAddrSpec );
1188  // check for the obsolete form of display-name (as comment):
1189  eatWhiteSpace( scursor, send );
1190  if ( scursor != send && *scursor == '(' ) {
1191  scursor++;
1192  if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) {
1193  return false;
1194  }
1195  }
1196  result.setName( stripQuotes( maybeDisplayName ) );
1197  return true;
1198  }
1199  scursor = oldscursor;
1200 
1201  // second, see if there's a display-name:
1202  if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) {
1203  // failed: reset cursor, note absent display-name
1204  maybeDisplayName.clear();
1205  scursor = oldscursor;
1206  } else {
1207  // succeeded: eat CFWS
1208  eatCFWS( scursor, send, isCRLF );
1209  if ( scursor == send ) {
1210  return false;
1211  }
1212  }
1213 
1214  // third, parse the angle-addr:
1215  if ( !parseAngleAddr( scursor, send, maybeAddrSpec, isCRLF ) ) {
1216  return false;
1217  }
1218 
1219  if ( maybeDisplayName.isNull() ) {
1220  // check for the obsolete form of display-name (as comment):
1221  eatWhiteSpace( scursor, send );
1222  if ( scursor != send && *scursor == '(' ) {
1223  scursor++;
1224  if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) {
1225  return false;
1226  }
1227  }
1228  }
1229 
1230  result.setName( stripQuotes( maybeDisplayName ) );
1231  result.setAddress( maybeAddrSpec );
1232  return true;
1233 }
1234 
1235 bool parseGroup( const char* &scursor, const char * const send,
1236  Address &result, bool isCRLF )
1237 {
1238  // group := display-name ":" [ mailbox-list / CFWS ] ";" [CFWS]
1239  //
1240  // equivalent to:
1241  // group := display-name ":" [ obs-mbox-list ] ";"
1242 
1243  eatCFWS( scursor, send, isCRLF );
1244  if ( scursor == send ) {
1245  return false;
1246  }
1247 
1248  // get display-name:
1249  QString maybeDisplayName;
1250  if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) {
1251  return false;
1252  }
1253 
1254  // get ":":
1255  eatCFWS( scursor, send, isCRLF );
1256  if ( scursor == send || *scursor != ':' ) {
1257  return false;
1258  }
1259 
1260  // KDE5 TODO: Don't expose displayName as public, but rather add setter for it that
1261  // automatically calls removeBidiControlChars
1262  result.displayName = removeBidiControlChars( maybeDisplayName );
1263 
1264  // get obs-mbox-list (may contain empty entries):
1265  scursor++;
1266  while ( scursor != send ) {
1267  eatCFWS( scursor, send, isCRLF );
1268  if ( scursor == send ) {
1269  return false;
1270  }
1271 
1272  // empty entry:
1273  if ( *scursor == ',' ) {
1274  scursor++;
1275  continue;
1276  }
1277 
1278  // empty entry ending the list:
1279  if ( *scursor == ';' ) {
1280  scursor++;
1281  return true;
1282  }
1283 
1284  Mailbox maybeMailbox;
1285  if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
1286  return false;
1287  }
1288  result.mailboxList.append( maybeMailbox );
1289 
1290  eatCFWS( scursor, send, isCRLF );
1291  // premature end:
1292  if ( scursor == send ) {
1293  return false;
1294  }
1295  // regular end of the list:
1296  if ( *scursor == ';' ) {
1297  scursor++;
1298  return true;
1299  }
1300  // eat regular list entry separator:
1301  if ( *scursor == ',' ) {
1302  scursor++;
1303  }
1304  }
1305  return false;
1306 }
1307 
1308 bool parseAddress( const char* &scursor, const char * const send,
1309  Address &result, bool isCRLF )
1310 {
1311  // address := mailbox / group
1312 
1313  eatCFWS( scursor, send, isCRLF );
1314  if ( scursor == send ) {
1315  return false;
1316  }
1317 
1318  // first try if it's a single mailbox:
1319  Mailbox maybeMailbox;
1320  const char * oldscursor = scursor;
1321  if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
1322  // yes, it is:
1323  result.displayName.clear();
1324  result.mailboxList.append( maybeMailbox );
1325  return true;
1326  }
1327  scursor = oldscursor;
1328 
1329  Address maybeAddress;
1330 
1331  // no, it's not a single mailbox. Try if it's a group:
1332  if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) ) {
1333  return false;
1334  }
1335 
1336  result = maybeAddress;
1337  return true;
1338 }
1339 
1340 bool parseAddressList( const char* &scursor, const char * const send,
1341  AddressList &result, bool isCRLF )
1342 {
1343  while ( scursor != send ) {
1344  eatCFWS( scursor, send, isCRLF );
1345  // end of header: this is OK.
1346  if ( scursor == send ) {
1347  return true;
1348  }
1349  // empty entry: ignore:
1350  if ( *scursor == ',' ) {
1351  scursor++;
1352  continue;
1353  }
1354  // broken clients might use ';' as list delimiter, accept that as well
1355  if ( *scursor == ';' ) {
1356  scursor++;
1357  continue;
1358  }
1359 
1360  // parse one entry
1361  Address maybeAddress;
1362  if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) {
1363  return false;
1364  }
1365  result.append( maybeAddress );
1366 
1367  eatCFWS( scursor, send, isCRLF );
1368  // end of header: this is OK.
1369  if ( scursor == send ) {
1370  return true;
1371  }
1372  // comma separating entries: eat it.
1373  if ( *scursor == ',' ) {
1374  scursor++;
1375  }
1376  }
1377  return true;
1378 }
1379 
1380 static QString asterisk = QString::fromLatin1( "*0*", 1 );
1381 static QString asteriskZero = QString::fromLatin1( "*0*", 2 );
1382 //static QString asteriskZeroAsterisk = QString::fromLatin1( "*0*", 3 );
1383 
1384 // FIXME: Get rid of the very ugly "QStringOrQPair" thing. At this level, we are supposed to work
1385 // on byte arrays, not strings! The result parameter should be a simple
1386 // QPair<QByteArray,QByteArray>, which is the attribute name and the value.
1387 bool parseParameter( const char* &scursor, const char * const send,
1388  QPair<QString,QStringOrQPair> &result, bool isCRLF )
1389 {
1390  // parameter = regular-parameter / extended-parameter
1391  // regular-parameter = regular-parameter-name "=" value
1392  // extended-parameter =
1393  // value = token / quoted-string
1394  //
1395  // note that rfc2231 handling is out of the scope of this function.
1396  // Therefore we return the attribute as QString and the value as
1397  // (start,length) tupel if we see that the value is encoded
1398  // (trailing asterisk), for parseParameterList to decode...
1399 
1400  eatCFWS( scursor, send, isCRLF );
1401  if ( scursor == send ) {
1402  return false;
1403  }
1404 
1405  //
1406  // parse the parameter name:
1407  //
1408  // FIXME: maybeAttribute should be a QByteArray
1409  QString maybeAttribute;
1410  if ( !parseToken( scursor, send, maybeAttribute, false /* no 8bit */ ) ) {
1411  return false;
1412  }
1413 
1414  eatCFWS( scursor, send, isCRLF );
1415  // premature end: not OK (haven't seen '=' yet).
1416  if ( scursor == send || *scursor != '=' ) {
1417  return false;
1418  }
1419  scursor++; // eat '='
1420 
1421  eatCFWS( scursor, send, isCRLF );
1422  if ( scursor == send ) {
1423  // don't choke on attribute=, meaning the value was omitted:
1424  if ( maybeAttribute.endsWith( asterisk ) ) {
1425  KMIME_WARN << "attribute ends with \"*\", but value is empty!"
1426  "Chopping away \"*\".";
1427  maybeAttribute.truncate( maybeAttribute.length() - 1 );
1428  }
1429  result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() );
1430  return true;
1431  }
1432 
1433  const char * oldscursor = scursor;
1434 
1435  //
1436  // parse the parameter value:
1437  //
1438  QStringOrQPair maybeValue;
1439  if ( *scursor == '"' ) {
1440  // value is a quoted-string:
1441  scursor++;
1442  if ( maybeAttribute.endsWith( asterisk ) ) {
1443  // attributes ending with "*" designate extended-parameters,
1444  // which cannot have quoted-strings as values. So we remove the
1445  // trailing "*" to not confuse upper layers.
1446  KMIME_WARN << "attribute ends with \"*\", but value is a quoted-string!"
1447  "Chopping away \"*\".";
1448  maybeAttribute.truncate( maybeAttribute.length() - 1 );
1449  }
1450 
1451  if ( !parseGenericQuotedString( scursor, send, maybeValue.qstring, isCRLF ) ) {
1452  scursor = oldscursor;
1453  result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() );
1454  return false; // this case needs further processing by upper layers!!
1455  }
1456  } else {
1457  // value is a token:
1458  if ( !parseToken( scursor, send, maybeValue.qpair, false /* no 8bit */ ) ) {
1459  scursor = oldscursor;
1460  result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() );
1461  return false; // this case needs further processing by upper layers!!
1462  }
1463  }
1464 
1465  result = qMakePair( maybeAttribute.toLower(), maybeValue );
1466  return true;
1467 }
1468 
1469 // FIXME: Get rid of QStringOrQPair: Use a simply QMap<QByteArray, QByteArray> for "result"
1470 // instead!
1471 bool parseRawParameterList( const char* &scursor, const char * const send,
1472  QMap<QString,QStringOrQPair> &result,
1473  bool isCRLF )
1474 {
1475  // we use parseParameter() consecutively to obtain a map of raw
1476  // attributes to raw values. "Raw" here means that we don't do
1477  // rfc2231 decoding and concatenation. This is left to
1478  // parseParameterList(), which will call this function.
1479  //
1480  // The main reason for making this chunk of code a separate
1481  // (private) method is that we can deal with broken parameters
1482  // _here_ and leave the rfc2231 handling solely to
1483  // parseParameterList(), which will still be enough work.
1484 
1485  while ( scursor != send ) {
1486  eatCFWS( scursor, send, isCRLF );
1487  // empty entry ending the list: OK.
1488  if ( scursor == send ) {
1489  return true;
1490  }
1491  // empty list entry: ignore.
1492  if ( *scursor == ';' ) {
1493  scursor++;
1494  continue;
1495  }
1496 
1497  QPair<QString,QStringOrQPair> maybeParameter;
1498  if ( !parseParameter( scursor, send, maybeParameter, isCRLF ) ) {
1499  // we need to do a bit of work if the attribute is not
1500  // NULL. These are the cases marked with "needs further
1501  // processing" in parseParameter(). Specifically, parsing of the
1502  // token or the quoted-string, which should represent the value,
1503  // failed. We take the easy way out and simply search for the
1504  // next ';' to start parsing again. (Another option would be to
1505  // take the text between '=' and ';' as value)
1506  if ( maybeParameter.first.isNull() ) {
1507  return false;
1508  }
1509  while ( scursor != send ) {
1510  if ( *scursor++ == ';' ) {
1511  goto IS_SEMICOLON;
1512  }
1513  }
1514  // scursor == send case: end of list.
1515  return true;
1516  IS_SEMICOLON:
1517  // *scursor == ';' case: parse next entry.
1518  continue;
1519  }
1520  // successful parsing brings us here:
1521  result.insert( maybeParameter.first, maybeParameter.second );
1522 
1523  eatCFWS( scursor, send, isCRLF );
1524  // end of header: ends list.
1525  if ( scursor == send ) {
1526  return true;
1527  }
1528  // regular separator: eat it.
1529  if ( *scursor == ';' ) {
1530  scursor++;
1531  }
1532  }
1533  return true;
1534 }
1535 
1536 static void decodeRFC2231Value( Codec* &rfc2231Codec,
1537  QTextCodec* &textcodec,
1538  bool isContinuation, QString &value,
1539  QPair<const char*,int> &source, QByteArray& charset )
1540 {
1541  //
1542  // parse the raw value into (charset,language,text):
1543  //
1544 
1545  const char * decBegin = source.first;
1546  const char * decCursor = decBegin;
1547  const char * decEnd = decCursor + source.second;
1548 
1549  if ( !isContinuation ) {
1550  // find the first single quote
1551  while ( decCursor != decEnd ) {
1552  if ( *decCursor == '\'' ) {
1553  break;
1554  } else {
1555  decCursor++;
1556  }
1557  }
1558 
1559  if ( decCursor == decEnd ) {
1560  // there wasn't a single single quote at all!
1561  // take the whole value to be in latin-1:
1562  KMIME_WARN << "No charset in extended-initial-value."
1563  "Assuming \"iso-8859-1\".";
1564  value += QString::fromLatin1( decBegin, source.second );
1565  return;
1566  }
1567 
1568  charset = QByteArray( decBegin, decCursor - decBegin );
1569 
1570  const char * oldDecCursor = ++decCursor;
1571  // find the second single quote (we ignore the language tag):
1572  while ( decCursor != decEnd ) {
1573  if ( *decCursor == '\'' ) {
1574  break;
1575  } else {
1576  decCursor++;
1577  }
1578  }
1579  if ( decCursor == decEnd ) {
1580  KMIME_WARN << "No language in extended-initial-value."
1581  "Trying to recover.";
1582  decCursor = oldDecCursor;
1583  } else {
1584  decCursor++;
1585  }
1586 
1587  // decCursor now points to the start of the
1588  // "extended-other-values":
1589 
1590  //
1591  // get the decoders:
1592  //
1593 
1594  bool matchOK = false;
1595  textcodec = KGlobal::charsets()->codecForName( QLatin1String( charset ), matchOK );
1596  if ( !matchOK ) {
1597  textcodec = 0;
1598  KMIME_WARN_UNKNOWN( Charset, charset );
1599  }
1600  }
1601 
1602  if ( !rfc2231Codec ) {
1603  rfc2231Codec = Codec::codecForName("x-kmime-rfc2231");
1604  assert( rfc2231Codec );
1605  }
1606 
1607  if ( !textcodec ) {
1608  value += QString::fromLatin1( decCursor, decEnd - decCursor );
1609  return;
1610  }
1611 
1612  Decoder * dec = rfc2231Codec->makeDecoder();
1613  assert( dec );
1614 
1615  //
1616  // do the decoding:
1617  //
1618 
1619  QByteArray buffer;
1620  buffer.resize( rfc2231Codec->maxDecodedSizeFor( decEnd - decCursor ) );
1621  QByteArray::Iterator bit = buffer.begin();
1622  QByteArray::ConstIterator bend = buffer.end();
1623 
1624  if ( !dec->decode( decCursor, decEnd, bit, bend ) ) {
1625  KMIME_WARN << rfc2231Codec->name()
1626  << "codec lies about its maxDecodedSizeFor()" << endl
1627  << "result may be truncated";
1628  }
1629 
1630  value += textcodec->toUnicode( buffer.begin(), bit - buffer.begin() );
1631 
1632  // kDebug() << "value now: \"" << value << "\"";
1633  // cleanup:
1634  delete dec;
1635 }
1636 
1637 // known issues:
1638 // - permutes rfc2231 continuations when the total number of parts
1639 // exceeds 10 (other-sections then becomes *xy, ie. two digits)
1640 
1641 bool parseParameterListWithCharset( const char* &scursor,
1642  const char * const send,
1643  QMap<QString,QString> &result,
1644  QByteArray& charset, bool isCRLF )
1645 {
1646 // parse the list into raw attribute-value pairs:
1647  QMap<QString,QStringOrQPair> rawParameterList;
1648  if (!parseRawParameterList( scursor, send, rawParameterList, isCRLF ) ) {
1649  return false;
1650  }
1651 
1652  if ( rawParameterList.isEmpty() ) {
1653  return true;
1654  }
1655 
1656  // decode rfc 2231 continuations and alternate charset encoding:
1657 
1658  // NOTE: this code assumes that what QMapIterator delivers is sorted
1659  // by the key!
1660 
1661  Codec * rfc2231Codec = 0;
1662  QTextCodec * textcodec = 0;
1663  QString attribute;
1664  QString value;
1665  enum Mode {
1666  NoMode = 0x0, Continued = 0x1, Encoded = 0x2
1667  };
1668 
1669  enum EncodingMode {
1670  NoEncoding,
1671  RFC2047,
1672  RFC2231
1673  };
1674 
1675  QMap<QString,QStringOrQPair>::Iterator it, end = rawParameterList.end();
1676 
1677  for ( it = rawParameterList.begin() ; it != end ; ++it ) {
1678  if ( attribute.isNull() || !it.key().startsWith( attribute ) ) {
1679  //
1680  // new attribute:
1681  //
1682 
1683  // store the last attribute/value pair in the result map now:
1684  if ( !attribute.isNull() ) {
1685  result.insert( attribute, value );
1686  }
1687  // and extract the information from the new raw attribute:
1688  value.clear();
1689  attribute = it.key();
1690  int mode = NoMode;
1691  EncodingMode encodingMode = NoEncoding;
1692 
1693  // is the value rfc2331-encoded?
1694  if ( attribute.endsWith( asterisk ) ) {
1695  attribute.truncate( attribute.length() - 1 );
1696  mode |= Encoded;
1697  encodingMode = RFC2231;
1698  }
1699  // is the value rfc2047-encoded?
1700  if( !(*it).qstring.isNull() && (*it).qstring.contains( QLatin1String( "=?" ) ) ) {
1701  mode |= Encoded;
1702  encodingMode = RFC2047;
1703  }
1704  // is the value continued?
1705  if ( attribute.endsWith( asteriskZero ) ) {
1706  attribute.truncate( attribute.length() - 2 );
1707  mode |= Continued;
1708  }
1709  //
1710  // decode if necessary:
1711  //
1712  if ( mode & Encoded ) {
1713  if ( encodingMode == RFC2231 ) {
1714  decodeRFC2231Value( rfc2231Codec, textcodec,
1715  false, /* isn't continuation */
1716  value, (*it).qpair, charset );
1717  }
1718  else if ( encodingMode == RFC2047 ) {
1719  value += decodeRFC2047String( (*it).qstring.toLatin1(), charset );
1720  }
1721  } else {
1722  // not encoded.
1723  if ( (*it).qpair.first ) {
1724  value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second );
1725  } else {
1726  value += (*it).qstring;
1727  }
1728  }
1729 
1730  //
1731  // shortcut-processing when the value isn't encoded:
1732  //
1733 
1734  if ( !(mode & Continued) ) {
1735  // save result already:
1736  result.insert( attribute, value );
1737  // force begin of a new attribute:
1738  attribute.clear();
1739  }
1740  } else { // it.key().startsWith( attribute )
1741  //
1742  // continuation
1743  //
1744 
1745  // ignore the section and trust QMap to have sorted the keys:
1746  if ( it.key().endsWith( asterisk ) ) {
1747  // encoded
1748  decodeRFC2231Value( rfc2231Codec, textcodec,
1749  true, /* is continuation */
1750  value, (*it).qpair, charset );
1751  } else {
1752  // not encoded
1753  if ( (*it).qpair.first ) {
1754  value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second );
1755  } else {
1756  value += (*it).qstring;
1757  }
1758  }
1759  }
1760  }
1761 
1762  // write last attr/value pair:
1763  if ( !attribute.isNull() ) {
1764  result.insert( attribute, value );
1765  }
1766 
1767  return true;
1768 }
1769 
1770 
1771 bool parseParameterList( const char* &scursor, const char * const send,
1772  QMap<QString,QString> &result, bool isCRLF )
1773 {
1774  QByteArray charset;
1775  return parseParameterListWithCharset( scursor, send, result, charset, isCRLF );
1776 }
1777 
1778 static const char * const stdDayNames[] = {
1779  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1780 };
1781 static const int stdDayNamesLen = sizeof stdDayNames / sizeof *stdDayNames;
1782 
1783 static bool parseDayName( const char* &scursor, const char * const send )
1784 {
1785  // check bounds:
1786  if ( send - scursor < 3 ) {
1787  return false;
1788  }
1789 
1790  for ( int i = 0 ; i < stdDayNamesLen ; ++i ) {
1791  if ( qstrnicmp( scursor, stdDayNames[i], 3 ) == 0 ) {
1792  scursor += 3;
1793  // kDebug() << "found" << stdDayNames[i];
1794  return true;
1795  }
1796  }
1797 
1798  return false;
1799 }
1800 
1801 static const char * const stdMonthNames[] = {
1802  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1803  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1804 };
1805 static const int stdMonthNamesLen =
1806  sizeof stdMonthNames / sizeof *stdMonthNames;
1807 
1808 static bool parseMonthName( const char* &scursor, const char * const send,
1809  int &result )
1810 {
1811  // check bounds:
1812  if ( send - scursor < 3 ) {
1813  return false;
1814  }
1815 
1816  for ( result = 0 ; result < stdMonthNamesLen ; ++result ) {
1817  if ( qstrnicmp( scursor, stdMonthNames[result], 3 ) == 0 ) {
1818  scursor += 3;
1819  return true;
1820  }
1821  }
1822 
1823  // not found:
1824  return false;
1825 }
1826 
1827 static const struct {
1828  const char * tzName;
1829  long int secsEastOfGMT;
1830 } timeZones[] = {
1831  // rfc 822 timezones:
1832  { "GMT", 0 },
1833  { "UT", 0 },
1834  { "EDT", -4*3600 },
1835  { "EST", -5*3600 },
1836  { "MST", -5*3600 },
1837  { "CST", -6*3600 },
1838  { "MDT", -6*3600 },
1839  { "MST", -7*3600 },
1840  { "PDT", -7*3600 },
1841  { "PST", -8*3600 },
1842  // common, non-rfc-822 zones:
1843  { "CET", 1*3600 },
1844  { "MET", 1*3600 },
1845  { "UTC", 0 },
1846  { "CEST", 2*3600 },
1847  { "BST", 1*3600 },
1848  // rfc 822 military timezones:
1849  { "Z", 0 },
1850  { "A", -1*3600 },
1851  { "B", -2*3600 },
1852  { "C", -3*3600 },
1853  { "D", -4*3600 },
1854  { "E", -5*3600 },
1855  { "F", -6*3600 },
1856  { "G", -7*3600 },
1857  { "H", -8*3600 },
1858  { "I", -9*3600 },
1859  // J is not used!
1860  { "K", -10*3600 },
1861  { "L", -11*3600 },
1862  { "M", -12*3600 },
1863  { "N", 1*3600 },
1864  { "O", 2*3600 },
1865  { "P", 3*3600 },
1866  { "Q", 4*3600 },
1867  { "R", 5*3600 },
1868  { "S", 6*3600 },
1869  { "T", 7*3600 },
1870  { "U", 8*3600 },
1871  { "V", 9*3600 },
1872  { "W", 10*3600 },
1873  { "X", 11*3600 },
1874  { "Y", 12*3600 },
1875 };
1876 static const int timeZonesLen = sizeof timeZones / sizeof *timeZones;
1877 
1878 static bool parseAlphaNumericTimeZone( const char* &scursor,
1879  const char * const send,
1880  long int &secsEastOfGMT,
1881  bool &timeZoneKnown )
1882 {
1883  // allow the timezone to be wrapped in quotes; bug 260761
1884  if ( *scursor == '"' ) {
1885  scursor++;
1886 
1887  if ( scursor == send ) {
1888  return false;
1889  }
1890  }
1891 
1892  QPair<const char*,int> maybeTimeZone( 0, 0 );
1893  if ( !parseToken( scursor, send, maybeTimeZone, false /*no 8bit*/ ) ) {
1894  return false;
1895  }
1896  for ( int i = 0 ; i < timeZonesLen ; ++i ) {
1897  if ( qstrnicmp( timeZones[i].tzName,
1898  maybeTimeZone.first, maybeTimeZone.second ) == 0 ) {
1899  scursor += maybeTimeZone.second;
1900  secsEastOfGMT = timeZones[i].secsEastOfGMT;
1901  timeZoneKnown = true;
1902 
1903  if ( *scursor == '"' ) {
1904  scursor++;
1905  }
1906 
1907  return true;
1908  }
1909  }
1910 
1911  // don't choke just because we don't happen to know the time zone
1912  KMIME_WARN_UNKNOWN( time zone,
1913  QByteArray( maybeTimeZone.first, maybeTimeZone.second ) );
1914  secsEastOfGMT = 0;
1915  timeZoneKnown = false;
1916  return true;
1917 }
1918 
1919 // parse a number and return the number of digits parsed:
1920 int parseDigits( const char* &scursor, const char * const send, int &result )
1921 {
1922  result = 0;
1923  int digits = 0;
1924  for ( ; scursor != send && isdigit( *scursor ) ; scursor++, digits++ ) {
1925  result *= 10;
1926  result += int( *scursor - '0' );
1927  }
1928  return digits;
1929 }
1930 
1931 static bool parseTimeOfDay( const char* &scursor, const char * const send,
1932  int &hour, int &min, int &sec, bool isCRLF=false )
1933 {
1934  // time-of-day := 2DIGIT [CFWS] ":" [CFWS] 2DIGIT [ [CFWS] ":" 2DIGIT ]
1935 
1936  //
1937  // 2DIGIT representing "hour":
1938  //
1939  if ( !parseDigits( scursor, send, hour ) ) {
1940  return false;
1941  }
1942 
1943  eatCFWS( scursor, send, isCRLF );
1944  if ( scursor == send || *scursor != ':' ) {
1945  return false;
1946  }
1947  scursor++; // eat ':'
1948 
1949  eatCFWS( scursor, send, isCRLF );
1950  if ( scursor == send ) {
1951  return false;
1952  }
1953 
1954  //
1955  // 2DIGIT representing "minute":
1956  //
1957  if ( !parseDigits( scursor, send, min ) ) {
1958  return false;
1959  }
1960 
1961  eatCFWS( scursor, send, isCRLF );
1962  if ( scursor == send ) {
1963  return true; // seconds are optional
1964  }
1965 
1966  //
1967  // let's see if we have a 2DIGIT representing "second":
1968  //
1969  if ( *scursor == ':' ) {
1970  // yepp, there are seconds:
1971  scursor++; // eat ':'
1972  eatCFWS( scursor, send, isCRLF );
1973  if ( scursor == send ) {
1974  return false;
1975  }
1976 
1977  if ( !parseDigits( scursor, send, sec ) ) {
1978  return false;
1979  }
1980  } else {
1981  sec = 0;
1982  }
1983 
1984  return true;
1985 }
1986 
1987 bool parseTime( const char* &scursor, const char * send,
1988  int &hour, int &min, int &sec, long int &secsEastOfGMT,
1989  bool &timeZoneKnown, bool isCRLF )
1990 {
1991  // time := time-of-day CFWS ( zone / obs-zone )
1992  //
1993  // obs-zone := "UT" / "GMT" /
1994  // "EST" / "EDT" / ; -0500 / -0400
1995  // "CST" / "CDT" / ; -0600 / -0500
1996  // "MST" / "MDT" / ; -0700 / -0600
1997  // "PST" / "PDT" / ; -0800 / -0700
1998  // "A"-"I" / "a"-"i" /
1999  // "K"-"Z" / "k"-"z"
2000 
2001  eatCFWS( scursor, send, isCRLF );
2002  if ( scursor == send ) {
2003  return false;
2004  }
2005 
2006  if ( !parseTimeOfDay( scursor, send, hour, min, sec, isCRLF ) ) {
2007  return false;
2008  }
2009 
2010  eatCFWS( scursor, send, isCRLF );
2011  // there might be no timezone but a year following
2012  if ( ( scursor == send ) || isdigit( *scursor ) ) {
2013  timeZoneKnown = false;
2014  secsEastOfGMT = 0;
2015  return true; // allow missing timezone
2016  }
2017 
2018  timeZoneKnown = true;
2019  if ( *scursor == '+' || *scursor == '-' ) {
2020  // remember and eat '-'/'+':
2021  const char sign = *scursor++;
2022  // numerical timezone:
2023  int maybeTimeZone;
2024  if ( parseDigits( scursor, send, maybeTimeZone ) != 4 ) {
2025  return false;
2026  }
2027  secsEastOfGMT = 60 * ( maybeTimeZone / 100 * 60 + maybeTimeZone % 100 );
2028  if ( sign == '-' ) {
2029  secsEastOfGMT *= -1;
2030  if ( secsEastOfGMT == 0 ) {
2031  timeZoneKnown = false; // -0000 means indetermined tz
2032  }
2033  }
2034  } else {
2035  // maybe alphanumeric timezone:
2036  if ( !parseAlphaNumericTimeZone( scursor, send, secsEastOfGMT, timeZoneKnown ) ) {
2037  return false;
2038  }
2039  }
2040  return true;
2041 }
2042 
2043 bool parseDateTime( const char* &scursor, const char * const send,
2044  KDateTime &result, bool isCRLF )
2045 {
2046  // Parsing date-time; strict mode:
2047  //
2048  // date-time := [ [CFWS] day-name [CFWS] "," ] ; wday
2049  // (expanded) [CFWS] 1*2DIGIT CFWS month-name CFWS 2*DIGIT [CFWS] ; date
2050  // time
2051  //
2052  // day-name := "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
2053  // month-name := "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
2054  // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
2055 
2056  result = KDateTime();
2057  QDateTime maybeDateTime;
2058 
2059  eatCFWS( scursor, send, isCRLF );
2060  if ( scursor == send ) {
2061  return false;
2062  }
2063 
2064  //
2065  // let's see if there's a day-of-week:
2066  //
2067  if ( parseDayName( scursor, send ) ) {
2068  eatCFWS( scursor, send, isCRLF );
2069  if ( scursor == send ) {
2070  return false;
2071  }
2072  // day-name should be followed by ',' but we treat it as optional:
2073  if ( *scursor == ',' ) {
2074  scursor++; // eat ','
2075  eatCFWS( scursor, send, isCRLF );
2076  }
2077  }
2078 
2079  int maybeMonth = -1;
2080  bool asctimeFormat = false;
2081 
2082  // ANSI-C asctime() format is: Wed Jun 30 21:49:08 1993
2083  if ( !isdigit( *scursor ) && parseMonthName( scursor, send, maybeMonth ) ) {
2084  asctimeFormat = true;
2085  eatCFWS( scursor, send, isCRLF );
2086  }
2087 
2088  //
2089  // 1*2DIGIT representing "day" (of month):
2090  //
2091  int maybeDay;
2092  if ( !parseDigits( scursor, send, maybeDay ) ) {
2093  return false;
2094  }
2095 
2096  eatCFWS( scursor, send, isCRLF );
2097  if ( scursor == send ) {
2098  return false;
2099  }
2100 
2101  // ignore ","; bug 54098
2102  if ( *scursor == ',' ) {
2103  scursor++;
2104  }
2105 
2106  //
2107  // month-name:
2108  //
2109  if ( !asctimeFormat && !parseMonthName( scursor, send, maybeMonth ) ) {
2110  return false;
2111  }
2112  if ( scursor == send ) {
2113  return false;
2114  }
2115  assert( maybeMonth >= 0 ); assert( maybeMonth <= 11 );
2116  ++maybeMonth; // 0-11 -> 1-12
2117 
2118  eatCFWS( scursor, send, isCRLF );
2119  if ( scursor == send ) {
2120  return false;
2121  }
2122 
2123  // check for "year HH:MM:SS" or only "HH:MM:SS" (or "H:MM:SS")
2124  bool timeAfterYear = true;
2125  if ( ( send - scursor > 3 ) && ( ( scursor[1] == ':' ) || ( scursor[2] == ':' ) ) ) {
2126  timeAfterYear = false; // first read time, then year
2127  }
2128 
2129  //
2130  // 2*DIGIT representing "year":
2131  //
2132  int maybeYear = 0;
2133 
2134  if ( timeAfterYear && !parseDigits( scursor, send, maybeYear ) ) {
2135  return false;
2136  }
2137 
2138  eatCFWS( scursor, send, isCRLF );
2139  if ( scursor == send ) {
2140  return false;
2141  }
2142 
2143  //
2144  // time
2145  //
2146  int maybeHour, maybeMinute, maybeSecond;
2147  long int secsEastOfGMT;
2148  bool timeZoneKnown = true;
2149 
2150  if ( !parseTime( scursor, send,
2151  maybeHour, maybeMinute, maybeSecond,
2152  secsEastOfGMT, timeZoneKnown, isCRLF ) ) {
2153  return false;
2154  }
2155 
2156  // in asctime() the year follows the time
2157  if ( !timeAfterYear ) {
2158  eatCFWS( scursor, send, isCRLF );
2159  if ( scursor == send ) {
2160  return false;
2161  }
2162 
2163  if ( !parseDigits( scursor, send, maybeYear ) ) {
2164  return false;
2165  }
2166  }
2167 
2168  // RFC 2822 4.3 processing:
2169  if ( maybeYear < 50 ) {
2170  maybeYear += 2000;
2171  } else if ( maybeYear < 1000 ) {
2172  maybeYear += 1900;
2173  }
2174  // else keep as is
2175  if ( maybeYear < 1900 ) {
2176  return false; // rfc2822, 3.3
2177  }
2178 
2179  maybeDateTime.setDate( QDate( maybeYear, maybeMonth, maybeDay ) );
2180  maybeDateTime.setTime( QTime( maybeHour, maybeMinute, maybeSecond ) );
2181 
2182  if ( !maybeDateTime.isValid() )
2183  return false;
2184 
2185  result = KDateTime( maybeDateTime, KDateTime::Spec( KDateTime::OffsetFromUTC, secsEastOfGMT ) );
2186  if ( !result.isValid() )
2187  return false;
2188  return true;
2189 }
2190 
2191 Headers::Base *extractFirstHeader( QByteArray &head )
2192 {
2193  int endOfFieldBody = 0;
2194  bool folded = false;
2195  Headers::Base *header = 0;
2196 
2197  int startOfFieldBody = head.indexOf( ':' );
2198  const int endOfFieldHeader = startOfFieldBody;
2199 
2200  if ( startOfFieldBody > -1 ) { //there is another header
2201  startOfFieldBody++; //skip the ':'
2202  if ( head[startOfFieldBody] == ' ' ) { // skip the space after the ':', if there
2203  startOfFieldBody++;
2204  }
2205  endOfFieldBody = findHeaderLineEnd( head, startOfFieldBody, &folded );
2206 
2207  QByteArray rawType = head.left( endOfFieldHeader );
2208  QByteArray rawFieldBody = head.mid( startOfFieldBody, endOfFieldBody - startOfFieldBody );
2209  if ( folded ) {
2210  rawFieldBody = unfoldHeader( rawFieldBody );
2211  }
2212  // We might get an invalid mail without a field name, don't crash on that.
2213  if ( !rawType.isEmpty() ) {
2214  header = HeaderFactory::self()->createHeader( rawType );
2215  }
2216  if( !header ) {
2217  //kWarning() << "Returning Generic header of type" << rawType;
2218  header = new Headers::Generic( rawType.constData() );
2219  }
2220  header->from7BitString( rawFieldBody );
2221 
2222  head.remove( 0, endOfFieldBody + 1 );
2223  } else {
2224  head.clear();
2225  }
2226 
2227  return header;
2228 }
2229 
2230 void extractHeaderAndBody( const QByteArray &content, QByteArray &header, QByteArray &body )
2231 {
2232  header.clear();
2233  body.clear();
2234 
2235  // empty header
2236  if ( content.startsWith( '\n' ) ) {
2237  body = content.right( content.length() - 1 );
2238  return;
2239  }
2240 
2241  int pos = content.indexOf( "\n\n", 0 );
2242  if ( pos > -1 ) {
2243  header = content.left( ++pos ); //header *must* end with "\n" !!
2244  body = content.mid( pos + 1, content.length() - pos - 1 );
2245  } else {
2246  header = content;
2247  }
2248 }
2249 
2250 Headers::Base::List parseHeaders( const QByteArray &head )
2251 {
2252  Headers::Base::List ret;
2253  Headers::Base *h;
2254 
2255  QByteArray copy = head;
2256  while( ( h = extractFirstHeader( copy ) ) ) {
2257  ret << h;
2258  }
2259 
2260  return ret;
2261 }
2262 
2263 } // namespace HeaderParsing
2264 
2265 } // namespace KMime
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Tue Dec 11 2012 12:13:07 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KMIME Library

Skip menu "KMIME Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.9.4 API Reference

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal