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