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

Skip menu "kdepimlibs-4.8.3 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