• Skip to content
  • Skip to link menu
KDE 4.7 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

KMIME Library

kmime_header_parsing.cpp
00001 /*  -*- c++ -*-
00002     kmime_header_parsing.cpp
00003 
00004     KMime, the KDE Internet mail/usenet news message library.
00005     Copyright (c) 2001-2002 Marc Mutz <mutz@kde.org>
00006 
00007     This library is free software; you can redistribute it and/or
00008     modify it under the terms of the GNU Library General Public
00009     License as published by the Free Software Foundation; either
00010     version 2 of the License, or (at your option) any later version.
00011 
00012     This library is distributed in the hope that it will be useful,
00013     but WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015     Library General Public License for more details.
00016 
00017     You should have received a copy of the GNU Library General Public License
00018     along with this library; see the file COPYING.LIB.  If not, write to
00019     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020     Boston, MA 02110-1301, USA.
00021 */
00022 
00023 #include "kmime_header_parsing.h"
00024 
00025 #include "kmime_codecs.h"
00026 #include "kmime_headerfactory_p.h"
00027 #include "kmime_headers.h"
00028 #include "kmime_util.h"
00029 #include "kmime_util_p.h"
00030 #include "kmime_dateformatter.h"
00031 #include "kmime_warning.h"
00032 
00033 #include <kglobal.h>
00034 #include <kcharsets.h>
00035 
00036 #include <QtCore/QTextCodec>
00037 #include <QtCore/QMap>
00038 #include <QtCore/QStringList>
00039 #include <QtCore/QUrl>
00040 
00041 #include <ctype.h> // for isdigit
00042 #include <cassert>
00043 
00044 using namespace KMime;
00045 using namespace KMime::Types;
00046 
00047 namespace KMime {
00048 
00049 namespace Types {
00050 
00051 // QUrl::fromAce is extremely expensive, so only use it when necessary.
00052 // Fortunately, the presence of IDNA is readily detected with a substring match...
00053 static inline QString QUrl_fromAce_wrapper( const QString & domain )
00054 {
00055     if ( domain.contains( QLatin1String( "xn--" ) ) )
00056         return QUrl::fromAce( domain.toLatin1() );
00057     else
00058         return domain;
00059 }
00060 
00061 static QString addr_spec_as_string( const AddrSpec & as, bool pretty )
00062 {
00063   if ( as.isEmpty() ) {
00064     return QString();
00065   }
00066 
00067   static QChar dotChar = QLatin1Char( '.' );
00068   static QChar backslashChar = QLatin1Char( '\\' );
00069   static QChar quoteChar = QLatin1Char( '"' );
00070 
00071   bool needsQuotes = false;
00072   QString result;
00073   result.reserve( as.localPart.length() + as.domain.length() + 1 );
00074   for ( int i = 0 ; i < as.localPart.length() ; ++i ) {
00075     const QChar ch = as.localPart.at( i );
00076     if ( ch == dotChar || isAText( ch.toLatin1() ) ) {
00077       result += ch;
00078     } else {
00079       needsQuotes = true;
00080       if ( ch == backslashChar || ch == quoteChar ) {
00081         result += backslashChar;
00082       }
00083       result += ch;
00084     }
00085   }
00086   const QString dom = pretty ? QUrl_fromAce_wrapper( as.domain ) : as.domain ;
00087   if ( needsQuotes ) {
00088     result = quoteChar + result + quoteChar;
00089   }
00090   if( dom.isEmpty() ) {
00091     return result;
00092   } else {
00093     result += QLatin1Char( '@' );
00094     result += dom;
00095     return result;
00096   }
00097 }
00098 
00099 QString AddrSpec::asString() const
00100 {
00101     return addr_spec_as_string( *this, false );
00102 }
00103 
00104 QString AddrSpec::asPrettyString() const
00105 {
00106     return addr_spec_as_string( *this, true );
00107 }
00108 
00109 bool AddrSpec::isEmpty() const
00110 {
00111   return localPart.isEmpty() && domain.isEmpty();
00112 }
00113 
00114 QByteArray Mailbox::address() const
00115 {
00116   return mAddrSpec.asString().toLatin1();
00117 }
00118 
00119 AddrSpec Mailbox::addrSpec() const
00120 {
00121   return mAddrSpec;
00122 }
00123 
00124 QString Mailbox::name() const
00125 {
00126   return mDisplayName;
00127 }
00128 
00129 void Mailbox::setAddress( const AddrSpec &addr )
00130 {
00131   mAddrSpec = addr;
00132 }
00133 
00134 void Mailbox::setAddress( const QByteArray &addr )
00135 {
00136   const char *cursor = addr.constData();
00137   if ( !HeaderParsing::parseAngleAddr( cursor,
00138                                        cursor + addr.length(), mAddrSpec ) ) {
00139     if ( !HeaderParsing::parseAddrSpec( cursor, cursor + addr.length(),
00140                                         mAddrSpec ) ) {
00141       kWarning() << "Invalid address";
00142       return;
00143     }
00144   }
00145 }
00146 
00147 void Mailbox::setName( const QString &name )
00148 {
00149   mDisplayName = removeBidiControlChars( name );
00150 }
00151 
00152 void Mailbox::setNameFrom7Bit( const QByteArray &name,
00153                                const QByteArray &defaultCharset )
00154 {
00155   QByteArray cs;
00156   setName( decodeRFC2047String( name, cs, defaultCharset, false ) );
00157 }
00158 
00159 bool Mailbox::hasAddress() const
00160 {
00161   return !mAddrSpec.isEmpty();
00162 }
00163 
00164 bool Mailbox::hasName() const
00165 {
00166   return !mDisplayName.isEmpty();
00167 }
00168 
00169 QString Mailbox::prettyAddress() const
00170 {
00171   return prettyAddress( QuoteNever );
00172 }
00173 
00174 QString Mailbox::prettyAddress( Quoting quoting ) const
00175 {
00176   if ( !hasName() ) {
00177     return QLatin1String( address() );
00178   }
00179   QString s = name();
00180   if ( quoting != QuoteNever ) {
00181     addQuotes( s, quoting == QuoteAlways /*bool force*/ );
00182   }
00183 
00184   if ( hasAddress() ) {
00185     s += QLatin1String(" <") + QLatin1String( address() ) + QLatin1Char('>');
00186   }
00187   return s;
00188 }
00189 
00190 void Mailbox::fromUnicodeString( const QString &s )
00191 {
00192   from7BitString( encodeRFC2047Sentence( s, "utf-8" ) );
00193 }
00194 
00195 void Mailbox::from7BitString( const QByteArray &s )
00196 {
00197   const char *cursor = s.constData();
00198   HeaderParsing::parseMailbox( cursor, cursor + s.length(), *this );
00199 }
00200 
00201 QByteArray KMime::Types::Mailbox::as7BitString( const QByteArray &encCharset ) const
00202 {
00203   if ( !hasName() ) {
00204     return address();
00205   }
00206   QByteArray rv;
00207   if ( isUsAscii( name() ) ) {
00208     QByteArray tmp = name().toLatin1();
00209     addQuotes( tmp, false );
00210     rv += tmp;
00211   } else {
00212     rv += encodeRFC2047String( name(), encCharset, true );
00213   }
00214   if ( hasAddress() ) {
00215     rv += " <" + address() + '>';
00216   }
00217   return rv;
00218 }
00219 
00220 } // namespace Types
00221 
00222 namespace HeaderParsing {
00223 
00224 // parse the encoded-word (scursor points to after the initial '=')
00225 bool parseEncodedWord( const char* &scursor, const char * const send,
00226                        QString &result, QByteArray &language,
00227                        QByteArray &usedCS, const QByteArray &defaultCS,
00228                        bool forceCS )
00229 {
00230   // make sure the caller already did a bit of the work.
00231   assert( *(scursor-1) == '=' );
00232 
00233   //
00234   // STEP 1:
00235   // scan for the charset/language portion of the encoded-word
00236   //
00237 
00238   char ch = *scursor++;
00239 
00240   if ( ch != '?' ) {
00241     // kDebug() << "first";
00242     //KMIME_WARN_PREMATURE_END_OF( EncodedWord );
00243     return false;
00244   }
00245 
00246   // remember start of charset (ie. just after the initial "=?") and
00247   // language (just after the first '*') fields:
00248   const char * charsetStart = scursor;
00249   const char * languageStart = 0;
00250 
00251   // find delimiting '?' (and the '*' separating charset and language
00252   // tags, if any):
00253   for ( ; scursor != send ; scursor++ ) {
00254     if ( *scursor == '?') {
00255       break;
00256     } else if ( *scursor == '*' && languageStart == 0 ) {
00257       languageStart = scursor + 1;
00258     }
00259   }
00260 
00261   // not found? can't be an encoded-word!
00262   if ( scursor == send || *scursor != '?' ) {
00263     // kDebug() << "second";
00264     KMIME_WARN_PREMATURE_END_OF( EncodedWord );
00265     return false;
00266   }
00267 
00268   // extract the language information, if any (if languageStart is 0,
00269   // language will be null, too):
00270   QByteArray maybeLanguage( languageStart, scursor - languageStart );
00271   // extract charset information (keep in mind: the size given to the
00272   // ctor is one off due to the \0 terminator):
00273   QByteArray maybeCharset( charsetStart,
00274                            ( languageStart ? languageStart - 1 : scursor ) - charsetStart );
00275 
00276   //
00277   // STEP 2:
00278   // scan for the encoding portion of the encoded-word
00279   //
00280 
00281   // remember start of encoding (just _after_ the second '?'):
00282   scursor++;
00283   const char * encodingStart = scursor;
00284 
00285   // find next '?' (ending the encoding tag):
00286   for ( ; scursor != send ; scursor++ ) {
00287     if ( *scursor == '?' ) {
00288       break;
00289     }
00290   }
00291 
00292   // not found? Can't be an encoded-word!
00293   if ( scursor == send || *scursor != '?' ) {
00294     // kDebug() << "third";
00295     KMIME_WARN_PREMATURE_END_OF( EncodedWord );
00296     return false;
00297   }
00298 
00299   // extract the encoding information:
00300   QByteArray maybeEncoding( encodingStart, scursor - encodingStart );
00301 
00302   // kDebug() << "parseEncodedWord: found charset == \"" << maybeCharset
00303   //         << "\"; language == \"" << maybeLanguage
00304   //         << "\"; encoding == \"" << maybeEncoding << "\"";
00305 
00306   //
00307   // STEP 3:
00308   // scan for encoded-text portion of encoded-word
00309   //
00310 
00311   // remember start of encoded-text (just after the third '?'):
00312   scursor++;
00313   const char * encodedTextStart = scursor;
00314 
00315   // find the '?=' sequence (ending the encoded-text):
00316   for ( ; scursor != send ; scursor++ ) {
00317     if ( *scursor == '?' ) {
00318       if ( scursor + 1 != send ) {
00319         if ( *( scursor + 1 ) != '=' ) { // We expect a '=' after the '?', but we got something else; ignore
00320           KMIME_WARN << "Stray '?' in q-encoded word, ignoring this.";
00321           continue;
00322         }
00323         else { // yep, found a '?=' sequence
00324           scursor += 2;
00325           break;
00326         }
00327       }
00328       else { // The '?' is the last char, but we need a '=' after it!
00329         KMIME_WARN_PREMATURE_END_OF( EncodedWord );
00330         return false;
00331       }
00332     }
00333   }
00334 
00335   if ( *( scursor - 2 ) != '?' || *( scursor - 1 ) != '=' ||
00336        scursor < encodedTextStart + 2 ) {
00337     KMIME_WARN_PREMATURE_END_OF( EncodedWord );
00338     return false;
00339   }
00340 
00341   // set end sentinel for encoded-text:
00342   const char * const encodedTextEnd = scursor - 2;
00343 
00344   //
00345   // STEP 4:
00346   // setup decoders for the transfer encoding and the charset
00347   //
00348 
00349   // try if there's a codec for the encoding found:
00350   Codec * codec = Codec::codecForName( maybeEncoding );
00351   if ( !codec ) {
00352     KMIME_WARN_UNKNOWN( Encoding, maybeEncoding );
00353     return false;
00354   }
00355 
00356   // get an instance of a corresponding decoder:
00357   Decoder * dec = codec->makeDecoder();
00358   assert( dec );
00359 
00360   // try if there's a (text)codec for the charset found:
00361   bool matchOK = false;
00362   QTextCodec *textCodec = 0;
00363   if ( forceCS || maybeCharset.isEmpty() ) {
00364     textCodec = KGlobal::charsets()->codecForName( QLatin1String( defaultCS ), matchOK );
00365     usedCS = cachedCharset( defaultCS );
00366   } else {
00367     textCodec = KGlobal::charsets()->codecForName( QLatin1String( maybeCharset ), matchOK );
00368     if ( !matchOK ) {  //no suitable codec found => use default charset
00369       textCodec = KGlobal::charsets()->codecForName( QLatin1String( defaultCS ), matchOK );
00370       usedCS = cachedCharset( defaultCS );
00371     } else {
00372       usedCS = cachedCharset( maybeCharset );
00373     }
00374   }
00375 
00376   if ( !matchOK || !textCodec ) {
00377     KMIME_WARN_UNKNOWN( Charset, maybeCharset );
00378     delete dec;
00379     return false;
00380   };
00381 
00382   // kDebug() << "mimeName(): \"" << textCodec->name() << "\"";
00383 
00384   // allocate a temporary buffer to store the 8bit text:
00385   int encodedTextLength = encodedTextEnd - encodedTextStart;
00386   QByteArray buffer;
00387   buffer.resize( codec->maxDecodedSizeFor( encodedTextLength ) );
00388   char *bbegin = buffer.data();
00389   char *bend = bbegin + buffer.length();
00390 
00391   //
00392   // STEP 5:
00393   // do the actual decoding
00394   //
00395 
00396   if ( !dec->decode( encodedTextStart, encodedTextEnd, bbegin, bend ) ) {
00397     KMIME_WARN << codec->name() << "codec lies about its maxDecodedSizeFor("
00398                << encodedTextLength << ")\nresult may be truncated";
00399   }
00400 
00401   result = textCodec->toUnicode( buffer.data(), bbegin - buffer.data() );
00402 
00403   // kDebug() << "result now: \"" << result << "\"";
00404   // cleanup:
00405   delete dec;
00406   language = maybeLanguage;
00407 
00408   return true;
00409 }
00410 
00411 static inline void eatWhiteSpace( const char* &scursor, const char * const send )
00412 {
00413   while ( scursor != send &&
00414           ( *scursor == ' ' || *scursor == '\n' ||
00415             *scursor == '\t' || *scursor == '\r' ) )
00416     scursor++;
00417 }
00418 
00419 bool parseAtom( const char * &scursor, const char * const send,
00420                 QString &result, bool allow8Bit )
00421 {
00422   QPair<const char*,int> maybeResult;
00423 
00424   if ( parseAtom( scursor, send, maybeResult, allow8Bit ) ) {
00425     result += QString::fromLatin1( maybeResult.first, maybeResult.second );
00426     return true;
00427   }
00428 
00429   return false;
00430 }
00431 
00432 bool parseAtom( const char * &scursor, const char * const send,
00433                 QPair<const char*,int> &result, bool allow8Bit )
00434 {
00435   bool success = false;
00436   const char *start = scursor;
00437 
00438   while ( scursor != send ) {
00439     signed char ch = *scursor++;
00440     if ( ch > 0 && isAText( ch ) ) {
00441       // AText: OK
00442       success = true;
00443     } else if ( allow8Bit && ch < 0 ) {
00444       // 8bit char: not OK, but be tolerant.
00445       KMIME_WARN_8BIT( ch );
00446       success = true;
00447     } else {
00448       // CTL or special - marking the end of the atom:
00449       // re-set sursor to point to the offending
00450       // char and return:
00451       scursor--;
00452       break;
00453     }
00454   }
00455   result.first = start;
00456   result.second = scursor - start;
00457   return success;
00458 }
00459 
00460 // FIXME: Remove this and the other parseToken() method. add a new one where "result" is a
00461 //        QByteArray.
00462 bool parseToken( const char * &scursor, const char * const send,
00463                  QString &result, bool allow8Bit )
00464 {
00465   QPair<const char*,int> maybeResult;
00466 
00467   if ( parseToken( scursor, send, maybeResult, allow8Bit ) ) {
00468     result += QString::fromLatin1( maybeResult.first, maybeResult.second );
00469     return true;
00470   }
00471 
00472   return false;
00473 }
00474 
00475 bool parseToken( const char * &scursor, const char * const send,
00476                  QPair<const char*,int> &result, bool allow8Bit )
00477 {
00478   bool success = false;
00479   const char * start = scursor;
00480 
00481   while ( scursor != send ) {
00482     signed char ch = *scursor++;
00483     if ( ch > 0 && isTText( ch ) ) {
00484       // TText: OK
00485       success = true;
00486     } else if ( allow8Bit && ch < 0 ) {
00487       // 8bit char: not OK, but be tolerant.
00488       KMIME_WARN_8BIT( ch );
00489       success = true;
00490     } else {
00491       // CTL or tspecial - marking the end of the atom:
00492       // re-set sursor to point to the offending
00493       // char and return:
00494       scursor--;
00495       break;
00496     }
00497   }
00498   result.first = start;
00499   result.second = scursor - start;
00500   return success;
00501 }
00502 
00503 #define READ_ch_OR_FAIL if ( scursor == send ) {        \
00504     KMIME_WARN_PREMATURE_END_OF( GenericQuotedString ); \
00505     return false;                                       \
00506   } else {                                              \
00507     ch = *scursor++;                                    \
00508   }
00509 
00510 // known issues:
00511 //
00512 // - doesn't handle quoted CRLF
00513 
00514 // FIXME: Why is result a QString? This should be a QByteArray, since at this level, we don't
00515 //        know about encodings yet!
00516 bool parseGenericQuotedString( const char* &scursor, const char * const send,
00517                                QString &result, bool isCRLF,
00518                                const char openChar, const char closeChar )
00519 {
00520   char ch;
00521   // We are in a quoted-string or domain-literal or comment and the
00522   // cursor points to the first char after the openChar.
00523   // We will apply unfolding and quoted-pair removal.
00524   // We return when we either encounter the end or unescaped openChar
00525   // or closeChar.
00526 
00527   assert( *(scursor-1) == openChar || *(scursor-1) == closeChar );
00528 
00529   while ( scursor != send ) {
00530     ch = *scursor++;
00531 
00532     if ( ch == closeChar || ch == openChar ) {
00533       // end of quoted-string or another opening char:
00534       // let caller decide what to do.
00535       return true;
00536     }
00537 
00538     switch( ch ) {
00539     case '\\':      // quoted-pair
00540       // misses "\" CRLF LWSP-char handling, see rfc822, 3.4.5
00541       READ_ch_OR_FAIL;
00542       KMIME_WARN_IF_8BIT( ch );
00543       result += QLatin1Char( ch );
00544       break;
00545     case '\r':
00546       // ###
00547       // The case of lonely '\r' is easy to solve, as they're
00548       // not part of Unix Line-ending conventions.
00549       // But I see a problem if we are given Unix-native
00550       // line-ending-mails, where we cannot determine anymore
00551       // whether a given '\n' was part of a CRLF or was occurring
00552       // on it's own.
00553       READ_ch_OR_FAIL;
00554       if ( ch != '\n' ) {
00555         // CR on it's own...
00556         KMIME_WARN_LONE( CR );
00557         result += QLatin1Char('\r');
00558         scursor--; // points to after the '\r' again
00559       } else {
00560         // CRLF encountered.
00561         // lookahead: check for folding
00562         READ_ch_OR_FAIL;
00563         if ( ch == ' ' || ch == '\t' ) {
00564           // correct folding;
00565           // position cursor behind the CRLF WSP (unfolding)
00566           // and add the WSP to the result
00567           result += QLatin1Char( ch );
00568         } else {
00569           // this is the "shouldn't happen"-case. There is a CRLF
00570           // inside a quoted-string without it being part of FWS.
00571           // We take it verbatim.
00572           KMIME_WARN_NON_FOLDING( CRLF );
00573           result += QLatin1String( "\r\n" );
00574           // the cursor is decremented again, so's we need not
00575           // duplicate the whole switch here. "ch" could've been
00576           // everything (incl. openChar or closeChar).
00577           scursor--;
00578         }
00579       }
00580       break;
00581     case '\n':
00582       // Note: CRLF has been handled above already!
00583       // ### LF needs special treatment, depending on whether isCRLF
00584       // is true (we can be sure a lonely '\n' was meant this way) or
00585       // false ('\n' alone could have meant LF or CRLF in the original
00586       // message. This parser assumes CRLF iff the LF is followed by
00587       // either WSP (folding) or NULL (premature end of quoted-string;
00588       // Should be fixed, since NULL is allowed as per rfc822).
00589       READ_ch_OR_FAIL;
00590       if ( !isCRLF && ( ch == ' ' || ch == '\t' ) ) {
00591         // folding
00592         // correct folding
00593         result += QLatin1Char( ch );
00594       } else {
00595         // non-folding
00596         KMIME_WARN_LONE( LF );
00597         result += QLatin1Char( '\n' );
00598         // pos is decremented, so's we need not duplicate the whole
00599         // switch here. ch could've been everything (incl. <">, "\").
00600         scursor--;
00601       }
00602       break;
00603     case '=':
00604     {
00605       // ### Work around broken clients that send encoded words in quoted-strings
00606       //     For example, older KMail versions.
00607       if( scursor == send )
00608         break;
00609 
00610       const char *oldscursor = scursor;
00611       QString tmp;
00612       QByteArray lang, charset;
00613       if( *scursor++ == '?' ) {
00614         --scursor;
00615         if( parseEncodedWord( scursor, send, tmp, lang, charset ) ) {
00616           result += tmp;
00617           break;
00618         } else {
00619           scursor = oldscursor;
00620         }
00621       } else {
00622         scursor = oldscursor;
00623       }
00624       // fall through
00625     }
00626     default:
00627       KMIME_WARN_IF_8BIT( ch );
00628       result += QLatin1Char( ch );
00629     }
00630   }
00631 
00632   return false;
00633 }
00634 
00635 // known issues:
00636 //
00637 // - doesn't handle encoded-word inside comments.
00638 
00639 bool parseComment( const char* &scursor, const char * const send,
00640                    QString &result, bool isCRLF, bool reallySave )
00641 {
00642   int commentNestingDepth = 1;
00643   const char *afterLastClosingParenPos = 0;
00644   QString maybeCmnt;
00645   const char *oldscursor = scursor;
00646 
00647   assert( *(scursor-1) == '(' );
00648 
00649   while ( commentNestingDepth ) {
00650     QString cmntPart;
00651     if ( parseGenericQuotedString( scursor, send, cmntPart, isCRLF, '(', ')' ) ) {
00652       assert( *(scursor-1) == ')' || *(scursor-1) == '(' );
00653       // see the kdoc for above function for the possible conditions
00654       // we have to check:
00655       switch ( *(scursor-1) ) {
00656       case ')':
00657         if ( reallySave ) {
00658           // add the chunk that's now surely inside the comment.
00659           result += maybeCmnt;
00660           result += cmntPart;
00661           if ( commentNestingDepth > 1 ) {
00662             // don't add the outermost ')'...
00663             result += QLatin1Char( ')' );
00664           }
00665           maybeCmnt.clear();
00666         }
00667         afterLastClosingParenPos = scursor;
00668         --commentNestingDepth;
00669         break;
00670       case '(':
00671         if ( reallySave ) {
00672           // don't add to "result" yet, because we might find that we
00673           // are already outside the (broken) comment...
00674           maybeCmnt += cmntPart;
00675           maybeCmnt += QLatin1Char( '(' );
00676         }
00677         ++commentNestingDepth;
00678         break;
00679       default: assert( 0 );
00680       } // switch
00681     } else {
00682       // !parseGenericQuotedString, ie. premature end
00683       if ( afterLastClosingParenPos ) {
00684         scursor = afterLastClosingParenPos;
00685       } else {
00686         scursor = oldscursor;
00687       }
00688       return false;
00689     }
00690   } // while
00691 
00692   return true;
00693 }
00694 
00695 // known issues: none.
00696 
00697 bool parsePhrase( const char* &scursor, const char * const send,
00698                   QString &result, bool isCRLF )
00699 {
00700   enum {
00701     None, Phrase, Atom, EncodedWord, QuotedString
00702   } found = None;
00703 
00704   QString tmp;
00705   QByteArray lang, charset;
00706   const char *successfullyParsed = 0;
00707   // only used by the encoded-word branch
00708   const char *oldscursor;
00709   // used to suppress whitespace between adjacent encoded-words
00710   // (rfc2047, 6.2):
00711   bool lastWasEncodedWord = false;
00712 
00713   while ( scursor != send ) {
00714     char ch = *scursor++;
00715     switch ( ch ) {
00716     case '.': // broken, but allow for intorop's sake
00717       if ( found == None ) {
00718         --scursor;
00719         return false;
00720       } else {
00721         if ( scursor != send && ( *scursor == ' ' || *scursor == '\t' ) ) {
00722           result += QLatin1String( ". " );
00723         } else {
00724           result += QLatin1Char( '.' );
00725         }
00726         successfullyParsed = scursor;
00727       }
00728       break;
00729     case '"': // quoted-string
00730       tmp.clear();
00731       if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) {
00732         successfullyParsed = scursor;
00733         assert( *(scursor-1) == '"' );
00734         switch ( found ) {
00735         case None:
00736           found = QuotedString;
00737           break;
00738         case Phrase:
00739         case Atom:
00740         case EncodedWord:
00741         case QuotedString:
00742           found = Phrase;
00743           result += QLatin1Char(' '); // rfc822, 3.4.4
00744           break;
00745         default:
00746           assert( 0 );
00747         }
00748         lastWasEncodedWord = false;
00749         result += tmp;
00750       } else {
00751         // premature end of quoted string.
00752         // What to do? Return leading '"' as special? Return as quoted-string?
00753         // We do the latter if we already found something, else signal failure.
00754         if ( found == None ) {
00755           return false;
00756         } else {
00757           result += QLatin1Char(' '); // rfc822, 3.4.4
00758           result += tmp;
00759           return true;
00760         }
00761       }
00762       break;
00763     case '(': // comment
00764       // parse it, but ignore content:
00765       tmp.clear();
00766       if ( parseComment( scursor, send, tmp, isCRLF,
00767                          false /*don't bother with the content*/ ) ) {
00768         successfullyParsed = scursor;
00769         lastWasEncodedWord = false; // strictly interpreting rfc2047, 6.2
00770       } else {
00771         if ( found == None ) {
00772           return false;
00773         } else {
00774           scursor = successfullyParsed;
00775           return true;
00776         }
00777       }
00778       break;
00779     case '=': // encoded-word
00780       tmp.clear();
00781       oldscursor = scursor;
00782       lang.clear();
00783       charset.clear();
00784       if ( parseEncodedWord( scursor, send, tmp, lang, charset ) ) {
00785         successfullyParsed = scursor;
00786         switch ( found ) {
00787         case None:
00788           found = EncodedWord;
00789           break;
00790         case Phrase:
00791         case EncodedWord:
00792         case Atom:
00793         case QuotedString:
00794           if ( !lastWasEncodedWord ) {
00795             result += QLatin1Char(' '); // rfc822, 3.4.4
00796           }
00797           found = Phrase;
00798           break;
00799         default: assert( 0 );
00800         }
00801         lastWasEncodedWord = true;
00802         result += tmp;
00803         break;
00804       } else {
00805         // parse as atom:
00806         scursor = oldscursor;
00807       }
00808       // fall though...
00809 
00810     default: //atom
00811       tmp.clear();
00812       scursor--;
00813       if ( parseAtom( scursor, send, tmp, true /* allow 8bit */ ) ) {
00814         successfullyParsed = scursor;
00815         switch ( found ) {
00816         case None:
00817           found = Atom;
00818           break;
00819         case Phrase:
00820         case Atom:
00821         case EncodedWord:
00822         case QuotedString:
00823           found = Phrase;
00824           result += QLatin1Char(' '); // rfc822, 3.4.4
00825           break;
00826         default:
00827           assert( 0 );
00828         }
00829         lastWasEncodedWord = false;
00830         result += tmp;
00831       } else {
00832         if ( found == None ) {
00833           return false;
00834         } else {
00835           scursor = successfullyParsed;
00836           return true;
00837         }
00838       }
00839     }
00840     eatWhiteSpace( scursor, send );
00841   }
00842 
00843   return found != None;
00844 }
00845 
00846 // FIXME: This should probably by QByteArray &result instead?
00847 bool parseDotAtom( const char* &scursor, const char * const send,
00848                    QString &result, bool isCRLF )
00849 {
00850   eatCFWS( scursor, send, isCRLF );
00851 
00852   // always points to just after the last atom parsed:
00853   const char *successfullyParsed;
00854 
00855   QString tmp;
00856   if ( !parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) {
00857     return false;
00858   }
00859   result += tmp;
00860   successfullyParsed = scursor;
00861 
00862   while ( scursor != send ) {
00863 
00864     // end of header or no '.' -> return
00865     if ( scursor == send || *scursor != '.' ) {
00866       return true;
00867     }
00868     scursor++; // eat '.'
00869 
00870     if ( scursor == send || !isAText( *scursor ) ) {
00871       // end of header or no AText, but this time following a '.'!:
00872       // reset cursor to just after last successfully parsed char and
00873       // return:
00874       scursor = successfullyParsed;
00875       return true;
00876     }
00877 
00878     // try to parse the next atom:
00879     QString maybeAtom;
00880     if ( !parseAtom( scursor, send, maybeAtom, false /*no 8bit*/ ) ) {
00881       scursor = successfullyParsed;
00882       return true;
00883     }
00884 
00885     result += QLatin1Char('.');
00886     result += maybeAtom;
00887     successfullyParsed = scursor;
00888   }
00889 
00890   scursor = successfullyParsed;
00891   return true;
00892 }
00893 
00894 void eatCFWS( const char* &scursor, const char * const send, bool isCRLF )
00895 {
00896   QString dummy;
00897 
00898   while ( scursor != send ) {
00899     const char *oldscursor = scursor;
00900 
00901     char ch = *scursor++;
00902 
00903     switch( ch ) {
00904     case ' ':
00905     case '\t': // whitespace
00906     case '\r':
00907     case '\n': // folding
00908       continue;
00909 
00910     case '(': // comment
00911       if ( parseComment( scursor, send, dummy, isCRLF, false /*don't save*/ ) ) {
00912         continue;
00913       }
00914       scursor = oldscursor;
00915       return;
00916 
00917     default:
00918       scursor = oldscursor;
00919       return;
00920     }
00921   }
00922 }
00923 
00924 bool parseDomain( const char* &scursor, const char * const send,
00925                   QString &result, bool isCRLF )
00926 {
00927   eatCFWS( scursor, send, isCRLF );
00928   if ( scursor == send ) {
00929     return false;
00930   }
00931 
00932   // domain := dot-atom / domain-literal / atom *("." atom)
00933   //
00934   // equivalent to:
00935   // domain = dot-atom / domain-literal,
00936   // since parseDotAtom does allow CFWS between atoms and dots
00937 
00938   if ( *scursor == '[' ) {
00939     // domain-literal:
00940     QString maybeDomainLiteral;
00941     // eat '[':
00942     scursor++;
00943     while ( parseGenericQuotedString( scursor, send, maybeDomainLiteral,
00944                                       isCRLF, '[', ']' ) ) {
00945       if ( scursor == send ) {
00946         // end of header: check for closing ']':
00947         if ( *(scursor-1) == ']' ) {
00948           // OK, last char was ']':
00949           result = maybeDomainLiteral;
00950           return true;
00951         } else {
00952           // not OK, domain-literal wasn't closed:
00953           return false;
00954         }
00955       }
00956       // we hit openChar in parseGenericQuotedString.
00957       // include it in maybeDomainLiteral and keep on parsing:
00958       if ( *(scursor-1) == '[' ) {
00959         maybeDomainLiteral += QLatin1Char('[');
00960         continue;
00961       }
00962       // OK, real end of domain-literal:
00963       result = maybeDomainLiteral;
00964       return true;
00965     }
00966   } else {
00967     // dot-atom:
00968     QString maybeDotAtom;
00969     if ( parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) {
00970       result = maybeDotAtom;
00971       // Domain may end with '.', if so preserve it'
00972       if ( scursor != send && *scursor == '.' ) {
00973         result += QLatin1Char('.');
00974         scursor++;
00975       }
00976       return true;
00977     }
00978   }
00979   return false;
00980 }
00981 
00982 bool parseObsRoute( const char* &scursor, const char* const send,
00983                     QStringList &result, bool isCRLF, bool save )
00984 {
00985   while ( scursor != send ) {
00986     eatCFWS( scursor, send, isCRLF );
00987     if ( scursor == send ) {
00988       return false;
00989     }
00990 
00991     // empty entry:
00992     if ( *scursor == ',' ) {
00993       scursor++;
00994       if ( save ) {
00995         result.append( QString() );
00996       }
00997       continue;
00998     }
00999 
01000     // empty entry ending the list:
01001     if ( *scursor == ':' ) {
01002       scursor++;
01003       if ( save ) {
01004         result.append( QString() );
01005       }
01006       return true;
01007     }
01008 
01009     // each non-empty entry must begin with '@':
01010     if ( *scursor != '@' ) {
01011       return false;
01012     } else {
01013       scursor++;
01014     }
01015 
01016     QString maybeDomain;
01017     if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) {
01018       return false;
01019     }
01020     if ( save ) {
01021       result.append( maybeDomain );
01022     }
01023 
01024     // eat the following (optional) comma:
01025     eatCFWS( scursor, send, isCRLF );
01026     if ( scursor == send ) {
01027       return false;
01028     }
01029     if ( *scursor == ':' ) {
01030       scursor++;
01031       return true;
01032     }
01033     if ( *scursor == ',' ) {
01034       scursor++;
01035     }
01036   }
01037 
01038   return false;
01039 }
01040 
01041 bool parseAddrSpec( const char* &scursor, const char * const send,
01042                     AddrSpec &result, bool isCRLF )
01043 {
01044   //
01045   // STEP 1:
01046   // local-part := dot-atom / quoted-string / word *("." word)
01047   //
01048   // this is equivalent to:
01049   // local-part := word *("." word)
01050 
01051   QString maybeLocalPart;
01052   QString tmp;
01053 
01054   while ( scursor != send ) {
01055     // first, eat any whitespace
01056     eatCFWS( scursor, send, isCRLF );
01057 
01058     char ch = *scursor++;
01059     switch ( ch ) {
01060     case '.': // dot
01061       maybeLocalPart += QLatin1Char('.');
01062       break;
01063 
01064     case '@':
01065       goto SAW_AT_SIGN;
01066       break;
01067 
01068     case '"': // quoted-string
01069       tmp.clear();
01070       if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) {
01071         maybeLocalPart += tmp;
01072       } else {
01073         return false;
01074       }
01075       break;
01076 
01077     default: // atom
01078       scursor--; // re-set scursor to point to ch again
01079       tmp.clear();
01080       if ( parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) {
01081         maybeLocalPart += tmp;
01082       } else {
01083         return false; // parseAtom can only fail if the first char is non-atext.
01084       }
01085       break;
01086     }
01087   }
01088 
01089   return false;
01090 
01091   //
01092   // STEP 2:
01093   // domain
01094   //
01095 
01096 SAW_AT_SIGN:
01097 
01098   assert( *(scursor-1) == '@' );
01099 
01100   QString maybeDomain;
01101   if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) {
01102     return false;
01103   }
01104 
01105   result.localPart = maybeLocalPart;
01106   result.domain = maybeDomain;
01107 
01108   return true;
01109 }
01110 
01111 bool parseAngleAddr( const char* &scursor, const char * const send,
01112                      AddrSpec &result, bool isCRLF )
01113 {
01114   // first, we need an opening angle bracket:
01115   eatCFWS( scursor, send, isCRLF );
01116   if ( scursor == send || *scursor != '<' ) {
01117     return false;
01118   }
01119   scursor++; // eat '<'
01120 
01121   eatCFWS( scursor, send, isCRLF );
01122   if ( scursor == send ) {
01123     return false;
01124   }
01125 
01126   if ( *scursor == '@' || *scursor == ',' ) {
01127     // obs-route: parse, but ignore:
01128     KMIME_WARN << "obsolete source route found! ignoring.";
01129     QStringList dummy;
01130     if ( !parseObsRoute( scursor, send, dummy,
01131                          isCRLF, false /* don't save */ ) ) {
01132       return false;
01133     }
01134     // angle-addr isn't complete until after the '>':
01135     if ( scursor == send ) {
01136       return false;
01137     }
01138   }
01139 
01140   // parse addr-spec:
01141   AddrSpec maybeAddrSpec;
01142   if ( !parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) {
01143     return false;
01144   }
01145 
01146   eatCFWS( scursor, send, isCRLF );
01147   if ( scursor == send || *scursor != '>' ) {
01148     return false;
01149   }
01150   scursor++;
01151 
01152   result = maybeAddrSpec;
01153   return true;
01154 
01155 }
01156 
01157 static QString stripQuotes( const QString &input )
01158 {
01159   const QLatin1Char quotes( '"' );
01160   if ( input.startsWith( quotes ) && input.endsWith( quotes ) ) {
01161     QString stripped( input.mid( 1, input.size() - 2 ) );
01162     return stripped;
01163   }
01164   else return input;
01165 }
01166 
01167 bool parseMailbox( const char* &scursor, const char * const send,
01168                    Mailbox &result, bool isCRLF )
01169 {
01170   eatCFWS( scursor, send, isCRLF );
01171   if ( scursor == send ) {
01172     return false;
01173   }
01174 
01175   AddrSpec maybeAddrSpec;
01176   QString maybeDisplayName;
01177 
01178   // first, try if it's a vanilla addr-spec:
01179   const char * oldscursor = scursor;
01180   if ( parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) {
01181     result.setAddress( maybeAddrSpec );
01182     // check for the obsolete form of display-name (as comment):
01183     eatWhiteSpace( scursor, send );
01184     if ( scursor != send && *scursor == '(' ) {
01185       scursor++;
01186       if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) {
01187         return false;
01188       }
01189     }
01190     result.setName( stripQuotes( maybeDisplayName ) );
01191     return true;
01192   }
01193   scursor = oldscursor;
01194 
01195   // second, see if there's a display-name:
01196   if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) {
01197     // failed: reset cursor, note absent display-name
01198     maybeDisplayName.clear();
01199     scursor = oldscursor;
01200   } else {
01201     // succeeded: eat CFWS
01202     eatCFWS( scursor, send, isCRLF );
01203     if ( scursor == send ) {
01204       return false;
01205     }
01206   }
01207 
01208   // third, parse the angle-addr:
01209   if ( !parseAngleAddr( scursor, send, maybeAddrSpec, isCRLF ) ) {
01210     return false;
01211   }
01212 
01213   if ( maybeDisplayName.isNull() ) {
01214     // check for the obsolete form of display-name (as comment):
01215     eatWhiteSpace( scursor, send );
01216     if ( scursor != send && *scursor == '(' ) {
01217       scursor++;
01218       if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) {
01219         return false;
01220       }
01221     }
01222   }
01223 
01224   result.setName( stripQuotes( maybeDisplayName ) );
01225   result.setAddress( maybeAddrSpec );
01226   return true;
01227 }
01228 
01229 bool parseGroup( const char* &scursor, const char * const send,
01230                  Address &result, bool isCRLF )
01231 {
01232   // group         := display-name ":" [ mailbox-list / CFWS ] ";" [CFWS]
01233   //
01234   // equivalent to:
01235   // group   := display-name ":" [ obs-mbox-list ] ";"
01236 
01237   eatCFWS( scursor, send, isCRLF );
01238   if ( scursor == send ) {
01239     return false;
01240   }
01241 
01242   // get display-name:
01243   QString maybeDisplayName;
01244   if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) {
01245     return false;
01246   }
01247 
01248   // get ":":
01249   eatCFWS( scursor, send, isCRLF );
01250   if ( scursor == send || *scursor != ':' ) {
01251     return false;
01252   }
01253 
01254   // KDE5 TODO: Don't expose displayName as public, but rather add setter for it that
01255   //            automatically calls removeBidiControlChars
01256   result.displayName = removeBidiControlChars( maybeDisplayName );
01257 
01258   // get obs-mbox-list (may contain empty entries):
01259   scursor++;
01260   while ( scursor != send ) {
01261     eatCFWS( scursor, send, isCRLF );
01262     if ( scursor == send ) {
01263       return false;
01264     }
01265 
01266     // empty entry:
01267     if ( *scursor == ',' ) {
01268       scursor++;
01269       continue;
01270     }
01271 
01272     // empty entry ending the list:
01273     if ( *scursor == ';' ) {
01274       scursor++;
01275       return true;
01276     }
01277 
01278     Mailbox maybeMailbox;
01279     if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
01280       return false;
01281     }
01282     result.mailboxList.append( maybeMailbox );
01283 
01284     eatCFWS( scursor, send, isCRLF );
01285     // premature end:
01286     if ( scursor == send ) {
01287       return false;
01288     }
01289     // regular end of the list:
01290     if ( *scursor == ';' ) {
01291       scursor++;
01292       return true;
01293     }
01294     // eat regular list entry separator:
01295     if ( *scursor == ',' ) {
01296       scursor++;
01297     }
01298   }
01299   return false;
01300 }
01301 
01302 bool parseAddress( const char* &scursor, const char * const send,
01303                    Address &result, bool isCRLF )
01304 {
01305   // address       := mailbox / group
01306 
01307   eatCFWS( scursor, send, isCRLF );
01308   if ( scursor == send ) {
01309     return false;
01310   }
01311 
01312   // first try if it's a single mailbox:
01313   Mailbox maybeMailbox;
01314   const char * oldscursor = scursor;
01315   if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) {
01316     // yes, it is:
01317     result.displayName.clear();
01318     result.mailboxList.append( maybeMailbox );
01319     return true;
01320   }
01321   scursor = oldscursor;
01322 
01323   Address maybeAddress;
01324 
01325   // no, it's not a single mailbox. Try if it's a group:
01326   if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) ) {
01327     return false;
01328   }
01329 
01330   result = maybeAddress;
01331   return true;
01332 }
01333 
01334 bool parseAddressList( const char* &scursor, const char * const send,
01335                        AddressList &result, bool isCRLF )
01336 {
01337   while ( scursor != send ) {
01338     eatCFWS( scursor, send, isCRLF );
01339     // end of header: this is OK.
01340     if ( scursor == send ) {
01341       return true;
01342     }
01343     // empty entry: ignore:
01344     if ( *scursor == ',' ) {
01345       scursor++;
01346       continue;
01347     }
01348     // broken clients might use ';' as list delimiter, accept that as well
01349     if ( *scursor == ';' ) {
01350       scursor++;
01351       continue;
01352     }
01353 
01354     // parse one entry
01355     Address maybeAddress;
01356     if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) {
01357       return false;
01358     }
01359     result.append( maybeAddress );
01360 
01361     eatCFWS( scursor, send, isCRLF );
01362     // end of header: this is OK.
01363     if ( scursor == send ) {
01364       return true;
01365     }
01366     // comma separating entries: eat it.
01367     if ( *scursor == ',' ) {
01368       scursor++;
01369     }
01370   }
01371   return true;
01372 }
01373 
01374 static QString asterisk = QString::fromLatin1( "*0*", 1 );
01375 static QString asteriskZero = QString::fromLatin1( "*0*", 2 );
01376 //static QString asteriskZeroAsterisk = QString::fromLatin1( "*0*", 3 );
01377 
01378 // FIXME: Get rid of the very ugly "QStringOrQPair" thing. At this level, we are supposed to work
01379 //        on byte arrays, not strings! The result parameter should be a simple
01380 //        QPair<QByteArray,QByteArray>, which is the attribute name and the value.
01381 bool parseParameter( const char* &scursor, const char * const send,
01382                      QPair<QString,QStringOrQPair> &result, bool isCRLF )
01383 {
01384   // parameter = regular-parameter / extended-parameter
01385   // regular-parameter = regular-parameter-name "=" value
01386   // extended-parameter =
01387   // value = token / quoted-string
01388   //
01389   // note that rfc2231 handling is out of the scope of this function.
01390   // Therefore we return the attribute as QString and the value as
01391   // (start,length) tupel if we see that the value is encoded
01392   // (trailing asterisk), for parseParameterList to decode...
01393 
01394   eatCFWS( scursor, send, isCRLF );
01395   if ( scursor == send ) {
01396     return false;
01397   }
01398 
01399   //
01400   // parse the parameter name:
01401   //
01402   // FIXME: maybeAttribute should be a QByteArray
01403   QString maybeAttribute;
01404   if ( !parseToken( scursor, send, maybeAttribute, false /* no 8bit */ ) ) {
01405     return false;
01406   }
01407 
01408   eatCFWS( scursor, send, isCRLF );
01409   // premature end: not OK (haven't seen '=' yet).
01410   if ( scursor == send || *scursor != '=' ) {
01411     return false;
01412   }
01413   scursor++; // eat '='
01414 
01415   eatCFWS( scursor, send, isCRLF );
01416   if ( scursor == send ) {
01417     // don't choke on attribute=, meaning the value was omitted:
01418     if ( maybeAttribute.endsWith( asterisk ) ) {
01419       KMIME_WARN << "attribute ends with \"*\", but value is empty!"
01420         "Chopping away \"*\".";
01421       maybeAttribute.truncate( maybeAttribute.length() - 1 );
01422     }
01423     result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() );
01424     return true;
01425   }
01426 
01427   const char * oldscursor = scursor;
01428 
01429   //
01430   // parse the parameter value:
01431   //
01432   QStringOrQPair maybeValue;
01433   if ( *scursor == '"' ) {
01434     // value is a quoted-string:
01435     scursor++;
01436     if ( maybeAttribute.endsWith( asterisk ) ) {
01437       // attributes ending with "*" designate extended-parameters,
01438       // which cannot have quoted-strings as values. So we remove the
01439       // trailing "*" to not confuse upper layers.
01440       KMIME_WARN << "attribute ends with \"*\", but value is a quoted-string!"
01441         "Chopping away \"*\".";
01442       maybeAttribute.truncate( maybeAttribute.length() - 1 );
01443     }
01444 
01445     if ( !parseGenericQuotedString( scursor, send, maybeValue.qstring, isCRLF ) ) {
01446       scursor = oldscursor;
01447       result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() );
01448       return false; // this case needs further processing by upper layers!!
01449     }
01450   } else {
01451     // value is a token:
01452     if ( !parseToken( scursor, send, maybeValue.qpair, false /* no 8bit */ ) ) {
01453       scursor = oldscursor;
01454       result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() );
01455       return false; // this case needs further processing by upper layers!!
01456     }
01457   }
01458 
01459   result = qMakePair( maybeAttribute.toLower(), maybeValue );
01460   return true;
01461 }
01462 
01463 // FIXME: Get rid of QStringOrQPair: Use a simply QMap<QByteArray, QByteArray> for "result"
01464 //        instead!
01465 bool parseRawParameterList( const char* &scursor, const char * const send,
01466                             QMap<QString,QStringOrQPair> &result,
01467                             bool isCRLF )
01468 {
01469   // we use parseParameter() consecutively to obtain a map of raw
01470   // attributes to raw values. "Raw" here means that we don't do
01471   // rfc2231 decoding and concatenation. This is left to
01472   // parseParameterList(), which will call this function.
01473   //
01474   // The main reason for making this chunk of code a separate
01475   // (private) method is that we can deal with broken parameters
01476   // _here_ and leave the rfc2231 handling solely to
01477   // parseParameterList(), which will still be enough work.
01478 
01479   while ( scursor != send ) {
01480     eatCFWS( scursor, send, isCRLF );
01481     // empty entry ending the list: OK.
01482     if ( scursor == send ) {
01483       return true;
01484     }
01485     // empty list entry: ignore.
01486     if ( *scursor == ';' ) {
01487       scursor++;
01488       continue;
01489     }
01490 
01491     QPair<QString,QStringOrQPair> maybeParameter;
01492     if ( !parseParameter( scursor, send, maybeParameter, isCRLF ) ) {
01493       // we need to do a bit of work if the attribute is not
01494       // NULL. These are the cases marked with "needs further
01495       // processing" in parseParameter(). Specifically, parsing of the
01496       // token or the quoted-string, which should represent the value,
01497       // failed. We take the easy way out and simply search for the
01498       // next ';' to start parsing again. (Another option would be to
01499       // take the text between '=' and ';' as value)
01500       if ( maybeParameter.first.isNull() ) {
01501         return false;
01502       }
01503       while ( scursor != send ) {
01504         if ( *scursor++ == ';' ) {
01505           goto IS_SEMICOLON;
01506         }
01507       }
01508       // scursor == send case: end of list.
01509       return true;
01510     IS_SEMICOLON:
01511       // *scursor == ';' case: parse next entry.
01512       continue;
01513     }
01514     // successful parsing brings us here:
01515     result.insert( maybeParameter.first, maybeParameter.second );
01516 
01517     eatCFWS( scursor, send, isCRLF );
01518     // end of header: ends list.
01519     if ( scursor == send ) {
01520       return true;
01521     }
01522     // regular separator: eat it.
01523     if ( *scursor == ';' ) {
01524       scursor++;
01525     }
01526   }
01527   return true;
01528 }
01529 
01530 static void decodeRFC2231Value( Codec* &rfc2231Codec,
01531                                 QTextCodec* &textcodec,
01532                                 bool isContinuation, QString &value,
01533                                 QPair<const char*,int> &source, QByteArray& charset )
01534 {
01535   //
01536   // parse the raw value into (charset,language,text):
01537   //
01538 
01539   const char * decBegin = source.first;
01540   const char * decCursor = decBegin;
01541   const char * decEnd = decCursor + source.second;
01542 
01543   if ( !isContinuation ) {
01544     // find the first single quote
01545     while ( decCursor != decEnd ) {
01546       if ( *decCursor == '\'' ) {
01547         break;
01548       } else {
01549         decCursor++;
01550       }
01551     }
01552 
01553     if ( decCursor == decEnd ) {
01554       // there wasn't a single single quote at all!
01555       // take the whole value to be in latin-1:
01556       KMIME_WARN << "No charset in extended-initial-value."
01557         "Assuming \"iso-8859-1\".";
01558       value += QString::fromLatin1( decBegin, source.second );
01559       return;
01560     }
01561 
01562     charset = QByteArray( decBegin, decCursor - decBegin );
01563 
01564     const char * oldDecCursor = ++decCursor;
01565     // find the second single quote (we ignore the language tag):
01566     while ( decCursor != decEnd ) {
01567       if ( *decCursor == '\'' ) {
01568         break;
01569       } else {
01570         decCursor++;
01571       }
01572     }
01573     if ( decCursor == decEnd ) {
01574       KMIME_WARN << "No language in extended-initial-value."
01575         "Trying to recover.";
01576       decCursor = oldDecCursor;
01577     } else {
01578       decCursor++;
01579     }
01580 
01581     // decCursor now points to the start of the
01582     // "extended-other-values":
01583 
01584     //
01585     // get the decoders:
01586     //
01587 
01588     bool matchOK = false;
01589     textcodec = KGlobal::charsets()->codecForName( QLatin1String( charset ), matchOK );
01590     if ( !matchOK ) {
01591       textcodec = 0;
01592       KMIME_WARN_UNKNOWN( Charset, charset );
01593     }
01594   }
01595 
01596   if ( !rfc2231Codec ) {
01597     rfc2231Codec = Codec::codecForName("x-kmime-rfc2231");
01598     assert( rfc2231Codec );
01599   }
01600 
01601   if ( !textcodec ) {
01602     value += QString::fromLatin1( decCursor, decEnd - decCursor );
01603     return;
01604   }
01605 
01606   Decoder * dec = rfc2231Codec->makeDecoder();
01607   assert( dec );
01608 
01609   //
01610   // do the decoding:
01611   //
01612 
01613   QByteArray buffer;
01614   buffer.resize( rfc2231Codec->maxDecodedSizeFor( decEnd - decCursor ) );
01615   QByteArray::Iterator bit = buffer.begin();
01616   QByteArray::ConstIterator bend = buffer.end();
01617 
01618   if ( !dec->decode( decCursor, decEnd, bit, bend ) ) {
01619     KMIME_WARN << rfc2231Codec->name()
01620                << "codec lies about its maxDecodedSizeFor()" << endl
01621                << "result may be truncated";
01622   }
01623 
01624   value += textcodec->toUnicode( buffer.begin(), bit - buffer.begin() );
01625 
01626   // kDebug() << "value now: \"" << value << "\"";
01627   // cleanup:
01628   delete dec;
01629 }
01630 
01631 // known issues:
01632 //  - permutes rfc2231 continuations when the total number of parts
01633 //    exceeds 10 (other-sections then becomes *xy, ie. two digits)
01634 
01635 bool parseParameterListWithCharset( const char* &scursor,
01636                                                 const char * const send,
01637                                                 QMap<QString,QString> &result,
01638                                                 QByteArray& charset, bool isCRLF )
01639 {
01640 // parse the list into raw attribute-value pairs:
01641   QMap<QString,QStringOrQPair> rawParameterList;
01642   if (!parseRawParameterList( scursor, send, rawParameterList, isCRLF ) ) {
01643     return false;
01644   }
01645 
01646   if ( rawParameterList.isEmpty() ) {
01647     return true;
01648   }
01649 
01650   // decode rfc 2231 continuations and alternate charset encoding:
01651 
01652   // NOTE: this code assumes that what QMapIterator delivers is sorted
01653   // by the key!
01654 
01655   Codec * rfc2231Codec = 0;
01656   QTextCodec * textcodec = 0;
01657   QString attribute;
01658   QString value;
01659   enum Mode {
01660     NoMode = 0x0, Continued = 0x1, Encoded = 0x2
01661   };
01662 
01663   enum EncodingMode {
01664     NoEncoding,
01665     RFC2047,
01666     RFC2231
01667   };
01668 
01669   QMap<QString,QStringOrQPair>::Iterator it, end = rawParameterList.end();
01670 
01671   for ( it = rawParameterList.begin() ; it != end ; ++it ) {
01672     if ( attribute.isNull() || !it.key().startsWith( attribute ) ) {
01673       //
01674       // new attribute:
01675       //
01676 
01677       // store the last attribute/value pair in the result map now:
01678       if ( !attribute.isNull() ) {
01679         result.insert( attribute, value );
01680       }
01681       // and extract the information from the new raw attribute:
01682       value.clear();
01683       attribute = it.key();
01684       int mode = NoMode;
01685       EncodingMode encodingMode = NoEncoding;
01686 
01687       // is the value rfc2331-encoded?
01688       if ( attribute.endsWith( asterisk ) ) {
01689         attribute.truncate( attribute.length() - 1 );
01690         mode |= Encoded;
01691         encodingMode = RFC2231;
01692       }
01693       // is the value rfc2047-encoded?
01694       if( !(*it).qstring.isNull() && (*it).qstring.contains( QLatin1String( "=?" ) ) ) {
01695         mode |= Encoded;
01696         encodingMode = RFC2047;
01697       }
01698       // is the value continued?
01699       if ( attribute.endsWith( asteriskZero ) ) {
01700         attribute.truncate( attribute.length() - 2 );
01701         mode |= Continued;
01702       }
01703       //
01704       // decode if necessary:
01705       //
01706       if ( mode & Encoded ) {
01707         if ( encodingMode == RFC2231 ) {
01708           decodeRFC2231Value( rfc2231Codec, textcodec,
01709                               false, /* isn't continuation */
01710                               value, (*it).qpair, charset );
01711         }
01712         else if ( encodingMode == RFC2047 ) {
01713           value += decodeRFC2047String( (*it).qstring.toLatin1(), charset );
01714         }
01715       } else {
01716         // not encoded.
01717         if ( (*it).qpair.first ) {
01718           value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second );
01719         } else {
01720           value += (*it).qstring;
01721         }
01722       }
01723 
01724       //
01725       // shortcut-processing when the value isn't encoded:
01726       //
01727 
01728       if ( !(mode & Continued) ) {
01729         // save result already:
01730         result.insert( attribute, value );
01731         // force begin of a new attribute:
01732         attribute.clear();
01733       }
01734     } else { // it.key().startsWith( attribute )
01735       //
01736       // continuation
01737       //
01738 
01739       // ignore the section and trust QMap to have sorted the keys:
01740       if ( it.key().endsWith( asterisk ) ) {
01741         // encoded
01742         decodeRFC2231Value( rfc2231Codec, textcodec,
01743                             true, /* is continuation */
01744                             value, (*it).qpair, charset );
01745       } else {
01746         // not encoded
01747         if ( (*it).qpair.first ) {
01748           value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second );
01749         } else {
01750           value += (*it).qstring;
01751         }
01752       }
01753     }
01754   }
01755 
01756   // write last attr/value pair:
01757   if ( !attribute.isNull() ) {
01758     result.insert( attribute, value );
01759   }
01760 
01761   return true;
01762 }
01763 
01764 
01765 bool parseParameterList( const char* &scursor, const char * const send,
01766                          QMap<QString,QString> &result, bool isCRLF )
01767 {
01768   QByteArray charset;
01769   return parseParameterListWithCharset( scursor, send, result, charset, isCRLF );
01770 }
01771 
01772 static const char * const stdDayNames[] = {
01773   "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
01774 };
01775 static const int stdDayNamesLen = sizeof stdDayNames / sizeof *stdDayNames;
01776 
01777 static bool parseDayName( const char* &scursor, const char * const send )
01778 {
01779   // check bounds:
01780   if ( send - scursor < 3 ) {
01781     return false;
01782   }
01783 
01784   for ( int i = 0 ; i < stdDayNamesLen ; ++i ) {
01785     if ( qstrnicmp( scursor, stdDayNames[i], 3 ) == 0 ) {
01786       scursor += 3;
01787       // kDebug() << "found" << stdDayNames[i];
01788       return true;
01789     }
01790   }
01791 
01792   return false;
01793 }
01794 
01795 static const char * const stdMonthNames[] = {
01796   "Jan", "Feb", "Mar", "Apr", "May", "Jun",
01797   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
01798 };
01799 static const int stdMonthNamesLen =
01800                               sizeof stdMonthNames / sizeof *stdMonthNames;
01801 
01802 static bool parseMonthName( const char* &scursor, const char * const send,
01803                             int &result )
01804 {
01805   // check bounds:
01806   if ( send - scursor < 3 ) {
01807     return false;
01808   }
01809 
01810   for ( result = 0 ; result < stdMonthNamesLen ; ++result ) {
01811     if ( qstrnicmp( scursor, stdMonthNames[result], 3 ) == 0 ) {
01812       scursor += 3;
01813       return true;
01814     }
01815   }
01816 
01817   // not found:
01818   return false;
01819 }
01820 
01821 static const struct {
01822   const char * tzName;
01823   long int secsEastOfGMT;
01824 } timeZones[] = {
01825   // rfc 822 timezones:
01826   { "GMT", 0 },
01827   { "UT", 0 },
01828   { "EDT", -4*3600 },
01829   { "EST", -5*3600 },
01830   { "MST", -5*3600 },
01831   { "CST", -6*3600 },
01832   { "MDT", -6*3600 },
01833   { "MST", -7*3600 },
01834   { "PDT", -7*3600 },
01835   { "PST", -8*3600 },
01836   // common, non-rfc-822 zones:
01837   { "CET", 1*3600 },
01838   { "MET", 1*3600 },
01839   { "UTC", 0 },
01840   { "CEST", 2*3600 },
01841   { "BST", 1*3600 },
01842   // rfc 822 military timezones:
01843   { "Z", 0 },
01844   { "A", -1*3600 },
01845   { "B", -2*3600 },
01846   { "C", -3*3600 },
01847   { "D", -4*3600 },
01848   { "E", -5*3600 },
01849   { "F", -6*3600 },
01850   { "G", -7*3600 },
01851   { "H", -8*3600 },
01852   { "I", -9*3600 },
01853   // J is not used!
01854   { "K", -10*3600 },
01855   { "L", -11*3600 },
01856   { "M", -12*3600 },
01857   { "N", 1*3600 },
01858   { "O", 2*3600 },
01859   { "P", 3*3600 },
01860   { "Q", 4*3600 },
01861   { "R", 5*3600 },
01862   { "S", 6*3600 },
01863   { "T", 7*3600 },
01864   { "U", 8*3600 },
01865   { "V", 9*3600 },
01866   { "W", 10*3600 },
01867   { "X", 11*3600 },
01868   { "Y", 12*3600 },
01869 };
01870 static const int timeZonesLen = sizeof timeZones / sizeof *timeZones;
01871 
01872 static bool parseAlphaNumericTimeZone( const char* &scursor,
01873                                        const char * const send,
01874                                        long int &secsEastOfGMT,
01875                                        bool &timeZoneKnown )
01876 {
01877   QPair<const char*,int> maybeTimeZone( 0, 0 );
01878   if ( !parseToken( scursor, send, maybeTimeZone, false /*no 8bit*/ ) ) {
01879     return false;
01880   }
01881   for ( int i = 0 ; i < timeZonesLen ; ++i ) {
01882     if ( qstrnicmp( timeZones[i].tzName,
01883                     maybeTimeZone.first, maybeTimeZone.second ) == 0 ) {
01884       scursor += maybeTimeZone.second;
01885       secsEastOfGMT = timeZones[i].secsEastOfGMT;
01886       timeZoneKnown = true;
01887       return true;
01888     }
01889   }
01890 
01891   // don't choke just because we don't happen to know the time zone
01892   KMIME_WARN_UNKNOWN( time zone,
01893                       QByteArray( maybeTimeZone.first, maybeTimeZone.second ) );
01894   secsEastOfGMT = 0;
01895   timeZoneKnown = false;
01896   return true;
01897 }
01898 
01899 // parse a number and return the number of digits parsed:
01900 int parseDigits( const char* &scursor, const char * const send, int &result )
01901 {
01902   result = 0;
01903   int digits = 0;
01904   for ( ; scursor != send && isdigit( *scursor ) ; scursor++, digits++ ) {
01905     result *= 10;
01906     result += int( *scursor - '0' );
01907   }
01908   return digits;
01909 }
01910 
01911 static bool parseTimeOfDay( const char* &scursor, const char * const send,
01912                             int &hour, int &min, int &sec, bool isCRLF=false )
01913 {
01914   // time-of-day := 2DIGIT [CFWS] ":" [CFWS] 2DIGIT [ [CFWS] ":" 2DIGIT ]
01915 
01916   //
01917   // 2DIGIT representing "hour":
01918   //
01919   if ( !parseDigits( scursor, send, hour ) ) {
01920     return false;
01921   }
01922 
01923   eatCFWS( scursor, send, isCRLF );
01924   if ( scursor == send || *scursor != ':' ) {
01925     return false;
01926   }
01927   scursor++; // eat ':'
01928 
01929   eatCFWS( scursor, send, isCRLF );
01930   if ( scursor == send ) {
01931     return false;
01932   }
01933 
01934   //
01935   // 2DIGIT representing "minute":
01936   //
01937   if ( !parseDigits( scursor, send, min ) ) {
01938     return false;
01939   }
01940 
01941   eatCFWS( scursor, send, isCRLF );
01942   if ( scursor == send ) {
01943     return true; // seconds are optional
01944   }
01945 
01946   //
01947   // let's see if we have a 2DIGIT representing "second":
01948   //
01949   if ( *scursor == ':' ) {
01950     // yepp, there are seconds:
01951     scursor++; // eat ':'
01952     eatCFWS( scursor, send, isCRLF );
01953     if ( scursor == send ) {
01954       return false;
01955     }
01956 
01957     if ( !parseDigits( scursor, send, sec ) ) {
01958       return false;
01959     }
01960   } else {
01961     sec = 0;
01962   }
01963 
01964   return true;
01965 }
01966 
01967 bool parseTime( const char* &scursor, const char * send,
01968                 int &hour, int &min, int &sec, long int &secsEastOfGMT,
01969                 bool &timeZoneKnown, bool isCRLF )
01970 {
01971   // time := time-of-day CFWS ( zone / obs-zone )
01972   //
01973   // obs-zone    := "UT" / "GMT" /
01974   //                "EST" / "EDT" / ; -0500 / -0400
01975   //                "CST" / "CDT" / ; -0600 / -0500
01976   //                "MST" / "MDT" / ; -0700 / -0600
01977   //                "PST" / "PDT" / ; -0800 / -0700
01978   //                "A"-"I" / "a"-"i" /
01979   //                "K"-"Z" / "k"-"z"
01980 
01981   eatCFWS( scursor, send, isCRLF );
01982   if ( scursor == send ) {
01983     return false;
01984   }
01985 
01986   if ( !parseTimeOfDay( scursor, send, hour, min, sec, isCRLF ) ) {
01987     return false;
01988   }
01989 
01990   eatCFWS( scursor, send, isCRLF );
01991   if ( scursor == send ) {
01992     timeZoneKnown = false;
01993     secsEastOfGMT = 0;
01994     return true; // allow missing timezone
01995   }
01996 
01997   timeZoneKnown = true;
01998   if ( *scursor == '+' || *scursor == '-' ) {
01999     // remember and eat '-'/'+':
02000     const char sign = *scursor++;
02001     // numerical timezone:
02002     int maybeTimeZone;
02003     if ( parseDigits( scursor, send, maybeTimeZone ) != 4 ) {
02004       return false;
02005     }
02006     secsEastOfGMT = 60 * ( maybeTimeZone / 100 * 60 + maybeTimeZone % 100 );
02007     if ( sign == '-' ) {
02008       secsEastOfGMT *= -1;
02009       if ( secsEastOfGMT == 0 ) {
02010         timeZoneKnown = false; // -0000 means indetermined tz
02011       }
02012     }
02013   } else {
02014     // maybe alphanumeric timezone:
02015     if ( !parseAlphaNumericTimeZone( scursor, send, secsEastOfGMT, timeZoneKnown ) ) {
02016       return false;
02017     }
02018   }
02019   return true;
02020 }
02021 
02022 bool parseDateTime( const char* &scursor, const char * const send,
02023                     KDateTime &result, bool isCRLF )
02024 {
02025   // Parsing date-time; strict mode:
02026   //
02027   // date-time   := [ [CFWS] day-name [CFWS] "," ]                      ; wday
02028   // (expanded)     [CFWS] 1*2DIGIT CFWS month-name CFWS 2*DIGIT [CFWS] ; date
02029   //                time
02030   //
02031   // day-name    := "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
02032   // month-name  := "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
02033   //                "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
02034 
02035   result = KDateTime();
02036   QDateTime maybeDateTime;
02037 
02038   eatCFWS( scursor, send, isCRLF );
02039   if ( scursor == send ) {
02040     return false;
02041   }
02042 
02043   //
02044   // let's see if there's a day-of-week:
02045   //
02046   if ( parseDayName( scursor, send ) ) {
02047     eatCFWS( scursor, send, isCRLF );
02048     if ( scursor == send ) {
02049       return false;
02050     }
02051     // day-name should be followed by ',' but we treat it as optional:
02052     if ( *scursor == ',' ) {
02053       scursor++; // eat ','
02054       eatCFWS( scursor, send, isCRLF );
02055     }
02056   }
02057 
02058   //
02059   // 1*2DIGIT representing "day" (of month):
02060   //
02061   int maybeDay;
02062   if ( !parseDigits( scursor, send, maybeDay ) ) {
02063     return false;
02064   }
02065 
02066   eatCFWS( scursor, send, isCRLF );
02067   if ( scursor == send ) {
02068     return false;
02069   }
02070 
02071   //
02072   // month-name:
02073   //
02074   int maybeMonth = 0;
02075   if ( !parseMonthName( scursor, send, maybeMonth ) ) {
02076     return false;
02077   }
02078   if ( scursor == send ) {
02079     return false;
02080   }
02081   assert( maybeMonth >= 0 ); assert( maybeMonth <= 11 );
02082   ++maybeMonth; // 0-11 -> 1-12
02083 
02084   eatCFWS( scursor, send, isCRLF );
02085   if ( scursor == send ) {
02086     return false;
02087   }
02088 
02089   //
02090   // 2*DIGIT representing "year":
02091   //
02092   int maybeYear;
02093   if ( !parseDigits( scursor, send, maybeYear ) ) {
02094     return false;
02095   }
02096   // RFC 2822 4.3 processing:
02097   if ( maybeYear < 50 ) {
02098     maybeYear += 2000;
02099   } else if ( maybeYear < 1000 ) {
02100     maybeYear += 1900;
02101   }
02102   // else keep as is
02103   if ( maybeYear < 1900 ) {
02104     return false; // rfc2822, 3.3
02105   }
02106 
02107   eatCFWS( scursor, send, isCRLF );
02108   if ( scursor == send ) {
02109     return false;
02110   }
02111 
02112   maybeDateTime.setDate( QDate( maybeYear, maybeMonth, maybeDay ) );
02113 
02114   //
02115   // time
02116   //
02117   int maybeHour, maybeMinute, maybeSecond;
02118   long int secsEastOfGMT;
02119   bool timeZoneKnown = true;
02120 
02121   if ( !parseTime( scursor, send,
02122                    maybeHour, maybeMinute, maybeSecond,
02123                    secsEastOfGMT, timeZoneKnown, isCRLF ) ) {
02124     return false;
02125   }
02126 
02127   maybeDateTime.setTime( QTime( maybeHour, maybeMinute, maybeSecond ) );
02128   if ( !maybeDateTime.isValid() )
02129     return false;
02130 
02131   result = KDateTime( maybeDateTime, KDateTime::Spec( KDateTime::OffsetFromUTC, secsEastOfGMT ) );
02132   if ( !result.isValid() )
02133     return false;
02134   return true;
02135 }
02136 
02137 Headers::Base *extractFirstHeader( QByteArray &head )
02138 {
02139   int endOfFieldBody = 0;
02140   bool folded = false;
02141   Headers::Base *header = 0;
02142 
02143   int startOfFieldBody = head.indexOf( ':' );
02144   const int endOfFieldHeader = startOfFieldBody;
02145 
02146   if ( startOfFieldBody > -1 ) {    //there is another header
02147     startOfFieldBody++; //skip the ':'
02148     if ( head[startOfFieldBody] == ' ' ) { // skip the space after the ':', if there
02149       startOfFieldBody++;
02150     }
02151     endOfFieldBody = findHeaderLineEnd( head, startOfFieldBody, &folded );
02152 
02153     QByteArray rawType = head.left( endOfFieldHeader );
02154     QByteArray rawFieldBody = head.mid( startOfFieldBody, endOfFieldBody - startOfFieldBody );
02155     if ( folded ) {
02156       rawFieldBody = unfoldHeader( rawFieldBody );
02157     }
02158     // We might get an invalid mail without a field name, don't crash on that.
02159     if ( !rawType.isEmpty() ) {
02160       header = HeaderFactory::self()->createHeader( rawType );
02161     }
02162     if( !header ) {
02163       //kWarning() << "Returning Generic header of type" << rawType;
02164       header = new Headers::Generic( rawType );
02165     }
02166     header->from7BitString( rawFieldBody );
02167 
02168     head.remove( 0, endOfFieldBody + 1 );
02169   } else {
02170     head.clear();
02171   }
02172 
02173   return header;
02174 }
02175 
02176 void extractHeaderAndBody( const QByteArray &content, QByteArray &header, QByteArray &body )
02177 {
02178   header.clear();
02179   body.clear();
02180 
02181   // empty header
02182   if ( content.startsWith( '\n' ) ) {
02183     body = content.right( content.length() - 1 );
02184     return;
02185   }
02186 
02187   int pos = content.indexOf( "\n\n", 0 );
02188   if ( pos > -1 ) {
02189     header = content.left( ++pos );  //header *must* end with "\n" !!
02190     body = content.mid( pos + 1, content.length() - pos - 1 );
02191   } else {
02192     header = content;
02193   }
02194 }
02195 
02196 Headers::Base::List parseHeaders( const QByteArray &head )
02197 {
02198   Headers::Base::List ret;
02199   Headers::Base *h;
02200 
02201   QByteArray copy = head;
02202   while( ( h = extractFirstHeader( copy ) ) ) {
02203     ret << h;
02204   }
02205 
02206   return ret;
02207 }
02208 
02209 } // namespace HeaderParsing
02210 
02211 } // namespace KMime

KMIME Library

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • 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
Generated for KDE-PIM Libraries by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal