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 Apr 30 2012 21:48:40 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Apr 30 2012 21:48:40 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.