diff --git a/kmime/CMakeLists.txt b/kmime/CMakeLists.txt index d893edcd1..a87dd2580 100644 --- a/kmime/CMakeLists.txt +++ b/kmime/CMakeLists.txt @@ -1,53 +1,55 @@ add_subdirectory( tests ) include_directories( ${CMAKE_SOURCE_DIR}/libkdepim/ ) include(CheckTimezone) configure_file (config-kmime.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kmime.h) ########### next target ############### set(kmime_LIB_SRCS kmime_charfreq.cpp kmime_util.cpp kmime_mdn.cpp kmime_codecs.cpp kmime_codec_base64.cpp kmime_codec_uuencode.cpp kmime_codec_qp.cpp kmime_codec_identity.cpp kmime_parsers.cpp kmime_header_parsing.cpp + kmime_headerfactory.cpp kmime_content.cpp kmime_contentindex.cpp kmime_headers.cpp kmime_message.cpp kmime_newsarticle.cpp kmime_dateformatter.cpp boolflags.cpp kautodeletehash.cpp ) kde4_add_library(kmime SHARED ${kmime_LIB_SRCS}) target_link_libraries(kmime ${KDE4_KDECORE_LIBS} ) set_target_properties(kmime PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}) install(TARGETS kmime EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) install( FILES boolflags.h kmime_export.h kmime_charfreq.h kmime_codecs.h kmime_content.h kmime_contentindex.h kmime_header_parsing.h + kmime_headerfactory.h kmime_headers.h kmime_message.h kmime_mdn.h kmime_newsarticle.h kmime_dateformatter.h kmime_util.h DESTINATION ${INCLUDE_INSTALL_DIR}/kmime COMPONENT Devel) diff --git a/kmime/kmime_codecs.cpp b/kmime/kmime_codecs.cpp index 1680a1f1f..5f1b78536 100644 --- a/kmime/kmime_codecs.cpp +++ b/kmime/kmime_codecs.cpp @@ -1,234 +1,234 @@ /* -*- c++ -*- KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling MIME data and defines the Codec class. @brief Defines the Codec class. @authors Marc Mutz \ */ #include "kmime_codecs.h" #include "kmime_util.h" #include "kmime_codec_base64.h" #include "kmime_codec_qp.h" #include "kmime_codec_uuencode.h" #include "kmime_codec_identity.h" #include "kautodeletehash.h" #include #include #include #include #include #include #include #include using namespace KMime; namespace KMime { // global list of KMime::Codec's //@cond PRIVATE KAutoDeleteHash *Codec::all = 0; K_GLOBAL_STATIC(QMutex, dictLock) //@endcond void Codec::cleanupCodec() { delete all; all = 0; } void Codec::fillDictionary() { //all->insert( "7bit", new SevenBitCodec() ); //all->insert( "8bit", new EightBitCodec() ); all->insert( "base64", new Base64Codec() ); all->insert( "quoted-printable", new QuotedPrintableCodec() ); all->insert( "b", new Rfc2047BEncodingCodec() ); all->insert( "q", new Rfc2047QEncodingCodec() ); all->insert( "x-kmime-rfc2231", new Rfc2231EncodingCodec() ); all->insert( "x-uuencode", new UUCodec() ); //all->insert( "binary", new BinaryCodec() ); } Codec *Codec::codecForName( const char *name ) { const QByteArray ba( name ); return codecForName( ba ); } Codec *Codec::codecForName( const QByteArray &name ) { dictLock->lock(); // protect "all" if ( !all ) { all = new KAutoDeleteHash(); qAddPostRoutine(cleanupCodec); fillDictionary(); } QByteArray lowerName = name; kAsciiToLower( lowerName.data() ); - Codec *codec = (*all)[ lowerName ]; + Codec *codec = (*all)[ lowerName ]; // FIXME: operator[] adds an entry into the hash dictLock->unlock(); if ( !codec ) { kDebug(5320) << "Unknown codec \"" << name << "\" requested!"; } return codec; } bool Codec::encode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend, bool withCRLF ) const { // get an encoder: Encoder *enc = makeEncoder( withCRLF ); assert( enc ); // encode and check for output buffer overflow: while ( !enc->encode( scursor, send, dcursor, dend ) ) { if ( dcursor == dend ) { delete enc; return false; // not enough space in output buffer } } // finish and check for output buffer overflow: while ( !enc->finish( dcursor, dend ) ) { if ( dcursor == dend ) { delete enc; return false; // not enough space in output buffer } } // cleanup and return: delete enc; return true; // successfully encoded. } QByteArray Codec::encode( const QByteArray &src, bool withCRLF ) const { // allocate buffer for the worst case: QByteArray result; result.resize( maxEncodedSizeFor( src.size(), withCRLF ) ); // set up iterators: QByteArray::ConstIterator iit = src.begin(); QByteArray::ConstIterator iend = src.end(); QByteArray::Iterator oit = result.begin(); QByteArray::ConstIterator oend = result.end(); // encode if ( !encode( iit, iend, oit, oend, withCRLF ) ) { kFatal() << name() << "codec lies about it's mEncodedSizeFor()"; } // shrink result to actual size: result.truncate( oit - result.begin() ); return result; } QByteArray Codec::decode( const QByteArray &src, bool withCRLF ) const { // allocate buffer for the worst case: QByteArray result; result.resize( maxDecodedSizeFor( src.size(), withCRLF ) ); // set up iterators: QByteArray::ConstIterator iit = src.begin(); QByteArray::ConstIterator iend = src.end(); QByteArray::Iterator oit = result.begin(); QByteArray::ConstIterator oend = result.end(); // decode if ( !decode( iit, iend, oit, oend, withCRLF ) ) { kFatal() << name() << "codec lies about it's maxDecodedSizeFor()"; } // shrink result to actual size: result.truncate( oit - result.begin() ); return result; } bool Codec::decode( const char* &scursor, const char * const send, char* &dcursor, const char * const dend, bool withCRLF ) const { // get a decoder: Decoder *dec = makeDecoder( withCRLF ); assert( dec ); // decode and check for output buffer overflow: while ( !dec->decode( scursor, send, dcursor, dend ) ) { if ( dcursor == dend ) { delete dec; return false; // not enough space in output buffer } } // finish and check for output buffer overflow: while ( !dec->finish( dcursor, dend ) ) { if ( dcursor == dend ) { delete dec; return false; // not enough space in output buffer } } // cleanup and return: delete dec; return true; // successfully encoded. } // write as much as possible off the output buffer. Return true if // flushing was complete, false if some chars could not be flushed. bool Encoder::flushOutputBuffer( char* &dcursor, const char * const dend ) { int i; // copy output buffer to output stream: for ( i = 0 ; dcursor != dend && i < mOutputBufferCursor ; ++i ) { *dcursor++ = mOutputBuffer[i]; } // calculate the number of missing chars: int numCharsLeft = mOutputBufferCursor - i; // push the remaining chars to the begin of the buffer: if ( numCharsLeft ) { ::memmove( mOutputBuffer, mOutputBuffer + i, numCharsLeft ); } // adjust cursor: mOutputBufferCursor = numCharsLeft; return !numCharsLeft; } } // namespace KMime diff --git a/kmime/kmime_content.cpp b/kmime/kmime_content.cpp index 77d107e3a..480995584 100644 --- a/kmime/kmime_content.cpp +++ b/kmime/kmime_content.cpp @@ -1,1211 +1,1069 @@ /* kmime_content.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause + Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the Content class. @brief Defines the Content class. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #include "kmime_content.h" #include "kmime_content_p.h" +#include "kmime_header_parsing.h" #include "kmime_parsers.h" #include "kmime_util_p.h" #include #include #include #include #include #include #include #include using namespace KMime; namespace KMime { Content::Content() : d_ptr( new ContentPrivate( this ) ) { } Content::Content( Content *parent ) : d_ptr( new ContentPrivate( this ) ) { d_ptr->parent = parent; } Content::Content( const QByteArray &h, const QByteArray &b ) : d_ptr( new ContentPrivate( this ) ) { d_ptr->head = h; d_ptr->body = b; } Content::Content( const QByteArray &h, const QByteArray &b, Content *parent ) : d_ptr( new ContentPrivate( this ) ) { d_ptr->head = h; d_ptr->body = b; d_ptr->parent = parent; } -Content::Content( ContentPrivate *d ) : - d_ptr( d ) +Content::Content( ContentPrivate *d ) + : d_ptr( d ) { } Content::~Content() { qDeleteAll( h_eaders ); h_eaders.clear(); delete d_ptr; d_ptr = 0; } bool Content::hasContent() const { return !d_ptr->head.isEmpty() || !d_ptr->body.isEmpty() || !d_ptr->contents.isEmpty(); } void Content::setContent( const QList &l ) { Q_D(Content); //qDebug("Content::setContent( const QList &l ) : start"); d->head.clear(); d->body.clear(); //usage of textstreams is much faster than simply appending the strings QTextStream hts( &( d->head ), QIODevice::WriteOnly ); QTextStream bts( &( d->body ), QIODevice::WriteOnly ); hts.setCodec( "ISO 8859-1" ); bts.setCodec( "ISO 8859-1" ); bool isHead = true; foreach ( const QByteArray& line, l ) { if ( isHead && line.isEmpty() ) { isHead = false; continue; } if ( isHead ) { hts << line << "\n"; } else { bts << line << "\n"; } } //qDebug("Content::setContent( const QList & l ) : finished"); } void Content::setContent( const QByteArray &s ) { Q_D(Content); d->head.clear(); d->body.clear(); // empty header if ( s.startsWith( '\n' ) ) { d->body = s.right( s.length() - 1 ); return; } int pos = s.indexOf( "\n\n", 0 ); if ( pos > -1 ) { d->head = s.left( ++pos ); //header *must* end with "\n" !! d->body = s.mid( pos + 1, s.length() - pos - 1 ); } else { d->head = s; } } QByteArray Content::head() const { return d_ptr->head; } void Content::setHead( const QByteArray &head ) { d_ptr->head = head; if ( !head.endsWith( '\n' ) ) d_ptr->head += '\n'; } QByteArray Content::body() const { return d_ptr->body; } void Content::setBody( const QByteArray &body ) { d_ptr->body = body; } -//parse the message, split multiple parts void Content::parse() { - Q_D(Content); - //qDebug("void Content::parse() : start"); + Q_D( Content ); + + // Clean up old headers and parse them again. qDeleteAll( h_eaders ); - h_eaders.clear(); + h_eaders = HeaderParsing::parseHeaders( d->head ); + foreach( Headers::Base *h, h_eaders ) { + h->setParent( this ); + } - // check this part has already been partioned into subparts. - // if this is the case, we will not try to reparse the body - // of this part. - if ( d->body.size() == 0 && !d->contents.isEmpty() ) { - // reparse all sub parts - foreach ( Content *c, d->contents ) { - c->parse(); - } - return; + // If we are frozen, save the body as-is. This is done because parsing + // changes the content (it loses preambles and epilogues, converts uuencode->mime, etc.) + if( d->frozen ) { + d->frozenBody = d->body; } + // Clean up old sub-Contents and parse them again. qDeleteAll( d->contents ); d->contents.clear(); - Headers::ContentType *ct = contentType(); - QByteArray tmp; - Content *c; - Headers::contentCategory cat; - - // just "text" as mimetype is suspicious, perhaps this article was - // generated by broken software, better check for uuencoded binaries - // No mimetype content can also contains such encoded data (unless it is just - // a text/plain message --that the test isText() after this block catches--) - if ( ct->mimeType() == "text" || ct->isEmpty() ) { - //non-mime body => check for uuencoded content - Parser::UUEncoded uup( d->body, rawHeader( "Subject" ) ); - - if ( uup.parse() ) { // yep, it is uuencoded - - if ( uup.isPartial() ) { - //this seems to be only a part of the message so we treat - //it as "message/partial" - ct->setMimeType( "message/partial" ); - //ct->setId( uniqueString() ); not needed yet - ct->setPartialParams( uup.partialCount(), uup.partialNumber() ); - contentTransferEncoding()->setEncoding( Headers::CE7Bit ); - } else { //it's a complete message => treat as "multipart/mixed" - //the whole content is now split into single parts, so it's safe - //to delete the message-body - d->body.clear(); - - //binary parts - for ( int i = 0; i < uup.binaryParts().count(); ++i ) { - c = new Content( this ); - //generate content with mime-compliant headers - tmp = "Content-Type: "; - tmp += uup.mimeTypes().at( i ); - tmp += "; name=\""; - tmp += uup.filenames().at( i ); - tmp += "\"\nContent-Transfer-Encoding: x-uuencode\nContent-Disposition: attachment; filename=\""; - tmp += uup.filenames().at( i ); - tmp += "\"\n\n"; - tmp += uup.binaryParts().at( i ); - c->setContent( tmp ); - addContent( c ); - } - - if ( !d->contents.isEmpty() && d->contents.first() ) { - //readd the plain text before the uuencoded part - d->contents.first()->setContent( - "Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n" + - uup.textPart() ); - d->contents.first()->contentType()->setMimeType( "text/plain" ); - } - } + if( ct->isText() ) { + // This content is either text, or of unknown type. + kDebug() << "Content-Type empty or text."; + + if( d->parseUuencoded() ) { + // This is actually uuencoded content generated by broken software. + kDebug() << "Parsed uuencoded content."; + } else if( d->parseYenc() ) { + // This is actually yenc content generated by broken software. + kDebug() << "Parsed yenc content."; } else { - Parser::YENCEncoded yenc( d->body ); - - if ( yenc.parse() ) { - /* If it is partial, just assume there is exactly one decoded part, - * and make this that part */ - if ( yenc.isPartial() ) { - ct->setMimeType( "message/partial" ); - //ct->setId( uniqueString() ); not needed yet - ct->setPartialParams( yenc.partialCount(), yenc.partialNumber() ); - contentTransferEncoding()->setEncoding( Headers::CEbinary ); - } else { //it's a complete message => treat as "multipart/mixed" - //the whole content is now split into single parts, so it's safe - //to delete the message-body - d->body.clear(); - - //binary parts - for ( int i=0; isetContent( tmp ); - - // the bodies of yenc message parts are binary data, not null-terminated strings: - c->setBody( yenc.binaryParts()[i] ); - - addContent( c ); - } - - if ( !d->contents.isEmpty() && d->contents.first() ) { - //readd the plain text before the uuencoded part - d->contents.first()->setContent( - "Content-Type: text/plain\nContent-Transfer-Encoding: 7Bit\n\n" + - yenc.textPart() ); - d->contents.first()->contentType()->setMimeType( "text/plain" ); - } - } - } + // This is just plain text. + kDebug() << "Parsed plain text."; } - } - - - if ( ct->isText() ) { // default is text/plain - return; //nothing to do - } + } else if( ct->isMultipart() ) { + // This content claims to be MIME multipart. + kDebug() << "Content-Type is multipart."; - - if ( ct->isMultipart() ) { //this is a multipart message - tmp = ct->boundary(); //get boundary-parameter - - if ( !tmp.isEmpty() ) { - Parser::MultiPart mpp( d->body, tmp ); - if ( mpp.parse() ) { //at least one part found - - if ( ct->isSubtype( "alternative" ) ) { //examine category for the sub-parts - cat = Headers::CCalternativePart; - } else { - cat = Headers::CCmixedPart; //default to "mixed" - } - - QList parts = mpp.parts(); - QList::Iterator it; - for ( it=parts.begin(); it != parts.end(); ++it ) { - //create a new Content for every part - c = new Content( this ); - c->setContent( *it ); - c->parse(); - c->contentType()->setCategory( cat ); //set category of the sub-part - d->contents.append( c ); - //qDebug("part:\n%s\n\n%s", c->h_ead.data(), c->b_ody.left(100).data()); - } - - //the whole content is now split into single parts, so it's safe delete the message-body - d->body.clear(); - } else { //sh*t, the parsing failed so we have to treat the message as "text/plain" instead - ct->setMimeType( "text/plain" ); - ct->setCharset( "US-ASCII" ); - } + if( d->parseMultipart() ) { + // This is actual MIME multipart content. + kDebug() << "Parsed MIME multipart."; + } else { + // Parsing failed; treat this content as "text/plain". + kWarning() << "Failed to parse multipart. Treating as text/plain."; + ct->setMimeType( "text/plain" ); + ct->setCharset( "US-ASCII" ); } + } else { + // This content is something else, like an image or something... + kDebug() << "Content-Type is" << ct->mimeType() << "; leaving untouched."; } } +bool Content::isFrozen() const +{ + return d_ptr->frozen; +} + +void Content::setFrozen( bool frozen ) +{ + d_ptr->frozen = frozen; +} void Content::assemble() { - Q_D(Content); - QByteArray newHead = assembleHeaders(); - KMime::Content m; - m.setContent( d->fullContent() ); - m.parse(); - - foreach ( Headers::Base *h, h_eaders ) { - if ( h->isXHeader() ) { - Headers::Base *oldH = m.headerByType( h->type() ); - if ( !oldH || ( oldH && oldH->as7BitString() != h->as7BitString() ) ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - } - } - if ( newHead != d->head ) {//the headers were modified - newHead += d->head; // keep unparsed headers - d->head = newHead; + Q_D( Content ); + if( d->frozen ) { + kDebug() << "Frozen; doing nothing."; + return; } - foreach ( Content *c, contents() ) { + d->head = assembleHeaders(); + foreach( Content *c, contents() ) { c->assemble(); } } QByteArray Content::assembleHeaders() { - Q_D(Content); QByteArray newHead; - KMime::Content m; - m.setContent( d->fullContent() ); - m.parse(); - - //Content-Type - Headers::Base *h = contentType( false ); - if ( h && !h->isEmpty() && h->as7BitString() != m.contentType()->as7BitString() ) { - newHead += contentType()->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Content-Transfer-Encoding - h = contentTransferEncoding( false ); - if ( h && !h->isEmpty() && h->as7BitString() != m.contentTransferEncoding()->as7BitString() ) { - newHead += contentTransferEncoding()->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Content-Description - h = contentDescription( false ); - if ( h && h->as7BitString() != m.contentDescription()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Content-Disposition - h = contentDisposition( false ); - if ( h && h->as7BitString() != m.contentDisposition()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - if ( newHead.isEmpty() ) { - newHead = d->head; - } else { + foreach( const Headers::Base *h, h_eaders ) { + kDebug() << "header" << h->type() << h->as7BitString(); + if( !h->isEmpty() ) { + newHead += h->as7BitString() + '\n'; + } } return newHead; } void Content::clear() { Q_D(Content); qDeleteAll( h_eaders ); h_eaders.clear(); clearContents(); d->head.clear(); d->body.clear(); } void Content::clearContents( bool del ) { Q_D(Content); if( del ) { qDeleteAll( d->contents ); } d->contents.clear(); } QByteArray Content::encodedContent( bool useCrLf ) { Q_D(Content); QByteArray e; - // hack to convert articles with uuencoded or yencoded binaries into - // proper mime-compliant articles - if ( !d->contents.isEmpty() ) { - bool convertNonMimeBinaries=false; - - // reencode non-mime binaries... - foreach ( Content *c, d->contents ) { - if ( ( c->contentTransferEncoding( true )->encoding() == Headers::CEuuenc ) || - ( c->contentTransferEncoding( true )->encoding() == Headers::CEbinary ) ) { - convertNonMimeBinaries = true; - c->setBody( KCodecs::base64Encode( c->decodedContent(), true ) + '\n' ); - c->contentTransferEncoding( true )->setEncoding(Headers::CEbase64); - c->contentTransferEncoding( true )->setDecoded( false ); - c->removeHeader("Content-Description"); - c->assemble(); - } - } - - // add proper mime headers... - if ( convertNonMimeBinaries ) { - int beg = 0, end = 0; - beg = d->head.indexOf( "MIME-Version: " ); - if ( beg >= 0 ) { - end = d->head.indexOf( '\n', beg ); - } - if ( beg >= 0 && end > beg ) { - d->head.remove( beg, end - beg ); - } - beg = d->head.indexOf( "Content-Type: " ); - if ( beg >= 0 ) { - end = d->head.indexOf( '\n', beg ); - } - if ( beg >= 0 && end > beg ) { - d->head.remove( beg, end - beg ); - } - beg = d->head.indexOf( "Content-Transfer-Encoding: " ); - if ( beg >= 0 ) { - end = d->head.indexOf( '\n', beg ); - } - if ( beg >= 0 && end > beg ) { - d->head.remove( beg, end - beg ); - } - - d->head += "MIME-Version: 1.0\n"; - d->head += contentType( true )->as7BitString() + '\n'; - d->head += contentTransferEncoding( true )->as7BitString() + '\n'; - } - } - - //head + // Head. e = d->head; e += '\n'; - //body - if ( !d->body.isEmpty() ) { //this message contains only one part - Headers::ContentTransferEncoding *enc=contentTransferEncoding(); + // Body. + if( d->frozen ) { + // This Content is frozen. + if( d->frozenBody.isEmpty() ) { + // This Content has never been parsed. + e += d->body; + } else { + // Use the body as it was before parsing. + e += d->frozenBody; + } + } else if( !d->body.isEmpty() ) { + // This is a single-part Content. + Headers::ContentTransferEncoding *enc = contentTransferEncoding(); + Q_ASSERT( enc->encoding() != Headers::CEuuenc ); + Q_ASSERT( enc->encoding() != Headers::CEbinary ); if (enc->needToEncode()) { if ( enc->encoding() == Headers::CEquPr ) { e += KCodecs::quotedPrintableEncode( d->body, false ); } else { e += KCodecs::base64Encode( d->body, true ); e += '\n'; } } else { e += d->body; } - } - else if ( !d->contents.isEmpty() ) { //this is a multipart message + } else if ( !d->contents.isEmpty() ) { + // This is a multipart Content. Headers::ContentType *ct=contentType(); QByteArray boundary = "\n--" + ct->boundary(); //add all (encoded) contents separated by boundaries foreach ( Content *c, d->contents ) { e+=boundary + '\n'; e += c->encodedContent( false ); // don't convert LFs here, we do that later!!!!! } //finally append the closing boundary e += boundary+"--\n"; }; if ( useCrLf ) { return LFtoCRLF( e ); } else { return e; } } QByteArray Content::decodedContent() { QByteArray temp, ret; Headers::ContentTransferEncoding *ec=contentTransferEncoding(); bool removeTrailingNewline=false; int size = d_ptr->body.length(); if ( size == 0 ) { return ret; } temp.resize( size ); memcpy( temp.data(), d_ptr->body.data(), size ); if ( ec->decoded() ) { ret = temp; removeTrailingNewline = true; } else { switch( ec->encoding() ) { case Headers::CEbase64 : KCodecs::base64Decode( temp, ret ); break; case Headers::CEquPr : ret = KCodecs::quotedPrintableDecode( d_ptr->body ); ret.resize( ret.size() - 1 ); // remove null-char removeTrailingNewline = true; break; case Headers::CEuuenc : KCodecs::uudecode( temp, ret ); break; case Headers::CEbinary : ret = temp; removeTrailingNewline = false; break; default : ret = temp; removeTrailingNewline = true; } } if ( removeTrailingNewline && ( ret.size() > 0 ) && ( ret[ret.size()-1] == '\n') ) { ret.resize( ret.size() - 1 ); } return ret; } QString Content::decodedText( bool trimText, bool removeTrailingNewlines ) { if ( !decodeText() ) { //this is not a text content !! return QString(); } bool ok = true; QTextCodec *codec = KGlobal::charsets()->codecForName( contentType()->charset(), ok ); QString s = codec->toUnicode( d_ptr->body.data(), d_ptr->body.length() ); if ( trimText && removeTrailingNewlines ) { int i; for ( i = s.length() - 1; i >= 0; --i ) { if ( !s[i].isSpace() ) { break; } } s.truncate( i + 1 ); } else { if ( s.right( 1 ) == "\n" ) { s.truncate( s.length() - 1 ); // remove trailing new-line } } return s; } void Content::fromUnicodeString( const QString &s ) { bool ok = true; QTextCodec *codec = KGlobal::charsets()->codecForName( contentType()->charset(), ok ); if ( !ok ) { // no suitable codec found => try local settings and hope the best ;-) codec = KGlobal::locale()->codecForEncoding(); QByteArray chset = KGlobal::locale()->encoding(); contentType()->setCharset( chset ); } d_ptr->body = codec->fromUnicode( s ); contentTransferEncoding()->setDecoded( true ); //text is always decoded } Content *Content::textContent() { Content *ret=0; //return the first content with mimetype=text/* if ( contentType()->isText() ) { ret = this; } else { foreach ( Content *c, d_ptr->contents ) { if ( ( ret = c->textContent() ) != 0 ) { break; } } } return ret; } Content::List Content::attachments( bool incAlternatives ) { List attachments; if ( d_ptr->contents.isEmpty() ) { attachments.append( this ); } else { foreach ( Content *c, d_ptr->contents ) { if ( !incAlternatives && c->contentType()->category() == Headers::CCalternativePart ) { continue; } else { attachments += c->attachments( incAlternatives ); } } } if ( isTopLevel() ) { Content *text = textContent(); if ( text ) { attachments.removeAll( text ); } } return attachments; } Content::List Content::contents() const { return d_ptr->contents; } void Content::addContent( Content *c, bool prepend ) { - Q_D(Content); - if ( d->contents.isEmpty() && !contentType()->isMultipart() ) { - // this message is not multipart yet + Q_D( Content ); - // first we convert the body to a content + // If this message is single-part; make it multipart first. + if( d->contents.isEmpty() && !contentType()->isMultipart() ) { + // The current body will be our first sub-Content. Content *main = new Content( this ); - //the Mime-Headers are needed, so we move them to the new content + // Move the MIME headers to the newly created sub-Content. + // NOTE: The other headers (RFC5322 headers like From:, To:, as well as X-headers + // are not moved to the subcontent; they remain with the top-level content. for ( Headers::Base::List::iterator it = h_eaders.begin(); it != h_eaders.end(); ) { if ( (*it)->isMimeHeader() ) { - // append to new content - main->h_eaders.append( *it ); - // and remove from this content + kDebug() << "moving header" << (*it)->type() << (*it)->as7BitString(); + // Add to new content. + main->setHeader( *it ); + // Remove from this content. it = h_eaders.erase( it ); } else { + kDebug() << "skipping header" << (*it)->type() << (*it)->as7BitString(); ++it; } } - //"main" is now part of a multipart/mixed message - main->contentType()->setCategory(Headers::CCmixedPart); + // Adjust the Content-Type of the newly created sub-Content. + main->contentType()->setCategory( Headers::CCmixedPart ); - //the head of "main" is empty, so we assemble it - main->assemble(); - - //now we can copy the body and append the new content; + // Move the body to the new subcontent. main->setBody( d->body ); + d->body.clear(); + + { + main->assemble(); // For debugging only. + kDebug() << "head of new subcontent" << main->head(); + } + + // Add the subcontent. d->contents.append( main ); - d->body.clear(); //no longer needed - //finally we have to convert this article to "multipart/mixed" - Headers::ContentType *ct=contentType(); + // Convert this content to "multipart/mixed". + Headers::ContentType *ct = contentType(); ct->setMimeType( "multipart/mixed" ); ct->setBoundary( multiPartBoundary() ); ct->setCategory( Headers::CCcontainer ); - contentTransferEncoding()->clear(); // 7Bit, decoded - + contentTransferEncoding()->clear(); // 7Bit, decoded. } - //here we actually add the content - if ( prepend ) { - d->contents.insert( 0, c ); + // Add the new content. + if( prepend ) { + d->contents.prepend( c ); } else { d->contents.append( c ); } - if ( c->parent() != this ) - c->setParent(this); + if( c->parent() != this ) { + // If the content was part of something else, this will remove it from there. + c->setParent( this ); + } } void Content::removeContent( Content *c, bool del ) { - Q_D(Content); - if ( d->contents.isEmpty() ) { // what the .. - return; - } + Q_D( Content ); + Q_ASSERT( d->contents.contains( c ) ); d->contents.removeAll( c ); if ( del ) { delete c; } else { - c->setParent( 0 ); + c->d_ptr->parent = 0; } - //only one content left => turn this message in a single-part - if ( d->contents.count() == 1 ) { + // If only one content is left, turn this content into a single-part. + if( d->contents.count() == 1 ) { Content *main = d->contents.first(); - //first we have to move the mime-headers - for ( Headers::Base::List::iterator it = main->h_eaders.begin(); - it != main->h_eaders.end(); ) { - if ( (*it)->isMimeHeader() ) { - kDebug(5320) << "Content::removeContent(Content *c, bool del) : mime-header moved:" - << (*it)->as7BitString(); - // first remove the old header - removeHeader( (*it)->type() ); - // then append to new content - h_eaders.append( *it ); - // and finally remove from this content - it = main->h_eaders.erase( it ); - } else { - ++it; - } + // Move all headers from the old subcontent to ourselves. + // NOTE: This also sets the new Content-Type. + foreach( Headers::Base *h, main->h_eaders ) { + kDebug() << "moving header" << h->type() << h->as7BitString(); + setHeader( h ); // Will remove the old one if present. } + main->h_eaders.clear(); - //now we can copy the body + // Move the body. d->body = main->body(); - //finally we can delete the content list - qDeleteAll( d->contents ); + // Delete the old subcontent. + delete main; d->contents.clear(); } } void Content::changeEncoding( Headers::contentEncoding e ) { Headers::ContentTransferEncoding *enc = contentTransferEncoding(); - if ( enc->encoding() == e ) { //nothing to do + if( enc->encoding() == e ) { + // Nothing to do. return; } - if ( decodeText() ) { - enc->setEncoding( e ); // text is not encoded until it's sent or saved - // so we just set the new encoding + if( decodeText() ) { + // This is textual content. Textual content is stored decoded. + Q_ASSERT( enc->decoded() ); + enc->setEncoding( e ); } else { - // this content contains non textual data, that has to be re-encoded - if ( e != Headers::CEbase64 ) { - //kWarning(5003) << "Content::changeEncoding() : non textual data" - // << "and encoding != base64 - this should not happen =>" - // << "forcing base64"; - e = Headers::CEbase64; - } - - if ( enc->encoding() != e ) { // ok, we reencode the content using base64 + // This is non-textual content. Re-encode it. + if( e == Headers::CEbase64 ) { d_ptr->body = KCodecs::base64Encode( decodedContent(), true ); d_ptr->body.append( "\n" ); - enc->setEncoding( e ); //set encoding + enc->setEncoding( e ); enc->setDecoded( false ); + } else { + // It only makes sense to convert binary stuff to base64. + Q_ASSERT( false ); } } } void Content::toStream( QTextStream &ts, bool scrambleFromLines ) { QByteArray ret = encodedContent( false ); if ( scrambleFromLines ) { // FIXME Why are only From lines with a preceding empty line considered? // And, of course, all lines starting with >*From have to be escaped // because otherwise the transformation is not revertable. ret.replace( "\n\nFrom ", "\n\n>From "); } ts << ret; } Headers::Generic *Content::getNextHeader( QByteArray &head ) { - return nextHeader( head ); + return nextHeader( head ); } Headers::Generic *Content::nextHeader( QByteArray &head ) { - int pos1=-1, pos2=0, len=head.length()-1; - bool folded( false ); - Headers::Generic *header=0; - - pos1 = head.indexOf( ": " ); - - if ( pos1 > -1 ) { //there is another header - pos2 = pos1 += 2; //skip the name - - if ( head[pos2] != '\n' ) { // check if the header is not empty - while ( 1 ) { - pos2 = head.indexOf( '\n', pos2 + 1 ); - if ( pos2 == -1 || pos2 == len || - ( head[pos2+1] != ' ' && head[pos2+1] != '\t' ) ) { - //break if we reach the end of the string, honor folded lines - break; - } else { - folded = true; - } - } - } - - if ( pos2 < 0 ) { - pos2 = len + 1; //take the rest of the string - } - - if ( !folded ) { - header = new Headers::Generic(head.left(pos1-2), this, head.mid(pos1, pos2-pos1)); - } else { - QByteArray hdrValue = head.mid( pos1, pos2 - pos1 ); - header = new Headers::Generic( head.left( pos1 - 2 ), this, unfoldHeader( hdrValue ) ); - } - - head.remove( 0, pos2 + 1 ); - } else { - head = ""; - } - - return header; + Headers::Base *header = HeaderParsing::extractFirstHeader( head ); + // Convert it from the real class to Generic. + Headers::Generic *ret = new Headers::Generic( header->type(), this ); + ret->from7BitString( header->as7BitString() ); + return ret; } Headers::Base *Content::getHeaderByType( const char *type ) { return headerByType( type ); } Headers::Base *Content::headerByType( const char *type ) { - if ( !type ) { - return 0; - } + kDebug() << "Type" << type; + Q_ASSERT( type && *type ); - //first we check if the requested header is already cached - foreach ( Headers::Base *h, h_eaders ) { - if ( h->is( type ) ) { - return h; //found + foreach( Headers::Base *h, h_eaders ) { + if( h->is( type ) ) { + kDebug() << "Found" << h->as7BitString(); + return h; // Found. } } - //now we look for it in the article head - Headers::Base *h = 0; - QByteArray raw=rawHeader( type ); - if ( !raw.isEmpty() ) { //ok, we found it - //choose a suitable header class - if ( strcasecmp( "Message-Id", type ) == 0 ) { - h = new Headers::MessageID( this, raw ); - } else if ( strcasecmp( "Subject", type ) == 0 ) { - h = new Headers::Subject( this, raw ); - } else if ( strcasecmp( "Date", type ) == 0 ) { - h = new Headers::Date( this, raw ); - } else if ( strcasecmp( "From", type ) == 0 ) { - h = new Headers::From( this, raw ); - } else if ( strcasecmp( "Organization", type ) == 0 ) { - h = new Headers::Organization( this, raw ); - } else if ( strcasecmp( "Reply-To", type ) == 0 ) { - h = new Headers::ReplyTo( this, raw ); - } else if ( strcasecmp( "Mail-Copies-To", type ) == 0 ) { - h = new Headers::MailCopiesTo( this, raw ); - } else if ( strcasecmp( "To", type ) == 0 ) { - h = new Headers::To( this, raw ); - } else if ( strcasecmp( "CC", type ) == 0 ) { - h = new Headers::Cc( this, raw ); - } else if ( strcasecmp( "BCC", type ) == 0 ) { - h = new Headers::Bcc( this, raw ); - } else if ( strcasecmp( "Newsgroups", type ) == 0 ) { - h = new Headers::Newsgroups( this, raw ); - } else if ( strcasecmp( "Followup-To", type ) == 0 ) { - h = new Headers::FollowUpTo( this, raw ); - } else if ( strcasecmp( "References", type ) == 0 ) { - h = new Headers::References( this, raw ); - } else if ( strcasecmp( "Lines", type ) == 0 ) { - h = new Headers::Lines( this, raw ); - } else if ( strcasecmp( "Content-Type", type ) == 0 ) { - h = new Headers::ContentType( this, raw ); - } else if ( strcasecmp( "Content-Transfer-Encoding", type ) == 0 ) { - h = new Headers::ContentTransferEncoding( this, raw ); - } else if ( strcasecmp( "Content-Disposition", type ) == 0 ) { - h = new Headers::ContentDisposition( this, raw ); - } else if ( strcasecmp( "Content-Description", type ) == 0 ) { - h = new Headers::ContentDescription( this, raw ); - } else if ( strcasecmp( "Content-Location", type ) == 0 ) { - h = new Headers::ContentLocation( this, raw ); - } else if ( strcasecmp( "Sender", type ) == 0 ) { - h = new Headers::Sender( this, raw ); - } else { - h = new Headers::Generic( type, this, raw ); - } - h_eaders.append( h ); //add to cache - return h; - } else { - return 0; //header not found - } + kDebug() << "Not found."; + return 0; // Not found. } -QList Content::headersByType( const char *type ) +Headers::Base::List Content::headersByType( const char *type ) { - QList result; + Q_ASSERT( type && *type ); + + Headers::Base::List result; - if ( !type ) { - return result; + foreach( Headers::Base *h, h_eaders ) { + if( h->is( type ) ) { + result << h; + } } - QList raw=rawHeaders( type ); - foreach( QByteArray header, raw ) - result.append( new Headers::Generic( type, this, header ) ); return result; } void Content::setHeader( Headers::Base *h ) { - if ( !h ) { - return; - } + Q_ASSERT( h ); removeHeader( h->type() ); + appendHeader( h ); +} + +void Content::appendHeader( Headers::Base *h ) +{ h_eaders.append( h ); + h->setParent( this ); +} + +void Content::prependHeader( Headers::Base *h ) +{ + h_eaders.prepend( h ); + h->setParent( this ); } bool Content::removeHeader( const char *type ) { for ( Headers::Base::List::iterator it = h_eaders.begin(); it != h_eaders.end(); ++it ) if ( (*it)->is(type) ) { delete (*it); h_eaders.erase( it ); return true; } return false; } bool Content::hasHeader( const char *type ) { return headerByType( type ) != 0; } -Headers::ContentType *Content::contentType( bool create ) -{ - Headers::ContentType *p=0; - return headerInstance( p, create ); -} - -Headers::ContentTransferEncoding *Content::contentTransferEncoding( bool create ) -{ - Headers::ContentTransferEncoding *p=0; - return headerInstance( p, create ); -} - -Headers::ContentDisposition *Content::contentDisposition( bool create ) -{ - Headers::ContentDisposition *p=0; - return headerInstance( p, create ); -} - -Headers::ContentDescription *Content::contentDescription( bool create ) -{ - Headers::ContentDescription *p=0; - return headerInstance( p, create ); -} - -Headers::ContentLocation *Content::contentLocation( bool create ) -{ - Headers::ContentLocation *p=0; - return headerInstance( p, create ); -} - int Content::size() { int ret = d_ptr->body.length(); if ( contentTransferEncoding()->encoding() == Headers::CEbase64 ) { return ret * 3 / 4; //base64 => 6 bit per byte } + // Not handling quoted-printable here since that requires actually + // converting the content, and that is O(size_of_content). + // For quoted-printable, this is only an approximate size. + return ret; } int Content::storageSize() const { const Q_D(Content); int s = d->head.size(); if ( d->contents.isEmpty() ) { s += d->body.size(); } else { foreach ( Content *c, d->contents ) { s += c->storageSize(); } } return s; } int Content::lineCount() const { const Q_D(Content); int ret = 0; if ( !isTopLevel() ) { ret += d->head.count( '\n' ); } ret += d->body.count( '\n' ); foreach ( Content *c, d->contents ) { ret += c->lineCount(); } return ret; } QByteArray Content::rawHeader( const char *name ) const { return KMime::extractHeader( d_ptr->head, name ); } QList Content::rawHeaders( const char *name ) const { return KMime::extractHeaders( d_ptr->head, name ); } bool Content::decodeText() { Q_D(Content); Headers::ContentTransferEncoding *enc = contentTransferEncoding(); if ( !contentType()->isText() ) { return false; //non textual data cannot be decoded here => use decodedContent() instead } if ( enc->decoded() ) { return true; //nothing to do } switch( enc->encoding() ) { case Headers::CEbase64 : d->body = KCodecs::base64Decode( d->body ); d->body.append( "\n" ); break; case Headers::CEquPr : d->body = KCodecs::quotedPrintableDecode( d->body ); break; case Headers::CEuuenc : d->body = KCodecs::uudecode( d->body ); d->body.append( "\n" ); break; case Headers::CEbinary : // nothing to decode d->body.append( "\n" ); default : break; } enc->setDecoded( true ); return true; } QByteArray Content::defaultCharset() const { return d_ptr->defaultCS; } void Content::setDefaultCharset( const QByteArray &cs ) { d_ptr->defaultCS = KMime::cachedCharset( cs ); foreach ( Content *c, d_ptr->contents ) { c->setDefaultCharset( cs ); } // reparse the part and its sub-parts in order // to clear cached header values parse(); } bool Content::forceDefaultCharset() const { return d_ptr->forceDefaultCS; } void Content::setForceDefaultCharset( bool b ) { d_ptr->forceDefaultCS = b; foreach ( Content *c, d_ptr->contents ) { c->setForceDefaultCharset( b ); } // reparse the part and its sub-parts in order // to clear cached header values parse(); } Content * KMime::Content::content( const ContentIndex &index ) const { if ( !index.isValid() ) { return const_cast( this ); } ContentIndex idx = index; unsigned int i = idx.pop() - 1; // one-based -> zero-based index if ( i < (unsigned int)d_ptr->contents.size() ) { return d_ptr->contents[i]->content( idx ); } else { return 0; } } ContentIndex KMime::Content::indexForContent( Content * content ) const { int i = d_ptr->contents.indexOf( content ); if ( i >= 0 ) { ContentIndex ci; ci.push( i + 1 ); // zero-based -> one-based index return ci; } // not found, we need to search recursively for ( int i = 0; i < d_ptr->contents.size(); ++i ) { ContentIndex ci = d_ptr->contents[i]->indexForContent( content ); if ( ci.isValid() ) { // found it ci.push( i + 1 ); // zero-based -> one-based index return ci; } } return ContentIndex(); // not found } bool Content::isTopLevel() const { return false; } void Content::setParent( Content* parent ) { //make sure the Content is only in the contents list of one parent object Content *oldParent = d_ptr->parent; if ( oldParent && oldParent->contents().contains( this ) ) { oldParent->removeContent( this ); } d_ptr->parent = parent; if ( parent && !parent->contents().contains( this ) ) { parent->addContent( this ); } } Content* Content::parent() const { return d_ptr->parent; } Content* Content::topLevel() const { Content *top = const_cast(this); Content *c = parent(); while ( c ) { top = c; c = c->parent(); } return top; } ContentIndex Content::index() const { Content* top = topLevel(); if ( top ) { return top->indexForContent( const_cast(this) ); } return indexForContent( const_cast(this) ); } -QByteArray ContentPrivate::fullContent() const +// @cond PRIVATE +#define kmime_mk_header_accessor( type, method ) \ +Headers::type *Content::method( bool create ) { \ + return header( create ); \ +} + +kmime_mk_header_accessor( ContentType, contentType ) +kmime_mk_header_accessor( ContentTransferEncoding, contentTransferEncoding ) +kmime_mk_header_accessor( ContentDisposition, contentDisposition ) +kmime_mk_header_accessor( ContentDescription, contentDescription ) +kmime_mk_header_accessor( ContentLocation, contentLocation ) +kmime_mk_header_accessor( ContentID, contentID ) + +#undef kmime_mk_header_accessor +// @endcond + + + +bool ContentPrivate::parseUuencoded() { - QByteArray content = head + "\n\n" + body; - if ( !contents.isEmpty() ) { //this is a multipart message - const Headers::ContentType *ct = const_cast(q_ptr)->contentType(); - QByteArray boundary = "\n--" + ct->boundary(); - - //add all (encoded) contents separated by boundaries - foreach ( Content *c, contents ) { - content += boundary + '\n'; - content += c->d_ptr->fullContent(); - } - //finally append the closing boundary - content += boundary + "--\n"; - }; + Q_Q( Content ); + Parser::UUEncoded uup( body, KMime::extractHeader( head, "Subject" ) ); + if( !uup.parse() ) { + return false; // Parsing failed. + } - return content; + Headers::ContentType *ct = q->contentType(); + ct->clear(); + + if( uup.isPartial() ) { + // This seems to be only a part of the message, so we treat it as "message/partial". + ct->setMimeType( "message/partial" ); + //ct->setId( uniqueString() ); not needed yet + ct->setPartialParams( uup.partialCount(), uup.partialNumber() ); + q->contentTransferEncoding()->setEncoding( Headers::CE7Bit ); + } else { + // This is a complete message, so treat it as "multipart/mixed". + body.clear(); + ct->setMimeType( "multipart/mixed" ); + ct->setBoundary( multiPartBoundary() ); + ct->setCategory( Headers::CCcontainer ); + q->contentTransferEncoding()->clear(); // 7Bit, decoded. + + // Add the plain text part first. + Q_ASSERT( contents.count() == 0 ); + { + Content *c = new Content( q ); + c->contentType()->setMimeType( "text/plain" ); + c->contentTransferEncoding()->setEncoding( Headers::CE7Bit ); + c->setBody( uup.textPart() ); + contents.append( c ); + } + + // Now add each of the binary parts as sub-Contents. + for( int i = 0; i < uup.binaryParts().count(); ++i ) { + Content *c = new Content( q ); + c->contentType()->setMimeType( uup.mimeTypes().at( i ) ); + c->contentType()->setName( uup.filenames().at( i ), QByteArray( /*charset*/ ) ); + c->contentTransferEncoding()->setEncoding( Headers::CEuuenc ); + c->contentDisposition()->setDisposition( Headers::CDattachment ); + c->contentDisposition()->setFilename( uup.filenames().at( i ) ); + c->setBody( uup.binaryParts().at( i ) ); + c->changeEncoding( Headers::CEbase64 ); // Convert to base64. + contents.append( c ); + } + } + + return true; // Parsing successful. } +bool ContentPrivate::parseYenc() +{ + Q_Q( Content ); + Parser::YENCEncoded yenc( body ); + if( !yenc.parse() ) { + return false; // Parsing failed. + } + + Headers::ContentType *ct = q->contentType(); + ct->clear(); + + if( yenc.isPartial() ) { + // Assume there is exactly one decoded part. Treat this as "message/partial". + ct->setMimeType( "message/partial" ); + //ct->setId( uniqueString() ); not needed yet + ct->setPartialParams( yenc.partialCount(), yenc.partialNumber() ); + q->contentTransferEncoding()->setEncoding( Headers::CEbinary ); + q->changeEncoding( Headers::CEbase64 ); // Convert to base64. + } else { + // This is a complete message, so treat it as "multipart/mixed". + body.clear(); + ct->setMimeType( "multipart/mixed" ); + ct->setBoundary( multiPartBoundary() ); + ct->setCategory( Headers::CCcontainer ); + q->contentTransferEncoding()->clear(); // 7Bit, decoded. + + // Add the plain text part first. + Q_ASSERT( contents.count() == 0 ); + { + Content *c = new Content( q ); + c->contentType()->setMimeType( "text/plain" ); + c->contentTransferEncoding()->setEncoding( Headers::CE7Bit ); + c->setBody( yenc.textPart() ); + contents.append( c ); + } + + // Now add each of the binary parts as sub-Contents. + for ( int i=0; icontentType()->setMimeType( yenc.mimeTypes().at( i ) ); + c->contentType()->setName( yenc.filenames().at( i ), QByteArray( /*charset*/ ) ); + c->contentTransferEncoding()->setEncoding( Headers::CEbinary ); + c->contentDisposition()->setDisposition( Headers::CDattachment ); + c->contentDisposition()->setFilename( yenc.filenames().at( i ) ); + c->setBody( yenc.binaryParts().at( i ) ); // Yenc bodies are binary. + c->changeEncoding( Headers::CEbase64 ); // Convert to base64. + contents.append( c ); + } + } + + return true; // Parsing successful. +} + +bool ContentPrivate::parseMultipart() +{ + Q_Q( Content ); + const Headers::ContentType *ct = q->contentType(); + const QByteArray boundary = ct->boundary(); + if( boundary.isEmpty() ) { + return false; // Parsing failed; invalid multipart content. + } + Parser::MultiPart mpp( body, boundary ); + if( !mpp.parse() ) { + return false; // Parsing failed. + } + + // Determine the category of the subparts (used in attachments()). + Headers::contentCategory cat; + if( ct->isSubtype( "alternative" ) ) { + cat = Headers::CCalternativePart; + } else { + cat = Headers::CCmixedPart; // Default to "mixed". + } + + // Create a sub-Content for every part. + Q_ASSERT( contents.isEmpty() ); + body.clear(); + QList parts = mpp.parts(); + foreach( const QByteArray &part, mpp.parts() ) { + Content *c = new Content( q ); + c->setContent( part ); + c->setFrozen( frozen ); + c->parse(); + c->contentType()->setCategory( cat ); + contents.append( c ); + } + + return true; // Parsing successful. +} } // namespace KMime diff --git a/kmime/kmime_content.h b/kmime/kmime_content.h index 2e18a0f31..282d69715 100644 --- a/kmime/kmime_content.h +++ b/kmime/kmime_content.h @@ -1,502 +1,655 @@ /* kmime_content.h KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause + Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the Content class. @brief Defines the Content class. @authors the KMime authors (see AUTHORS file), Volker Krause \ TODO: possible glossary terms: content encoding, transfer type, disposition, description header body attachment charset article */ #ifndef __KMIME_CONTENT_H__ #define __KMIME_CONTENT_H__ #include #include #include #include "kmime_export.h" #include "kmime_contentindex.h" #include "kmime_util.h" #include "kmime_headers.h" namespace KMime { class ContentPrivate; /** @brief A class that encapsulates @ref MIME encoded Content. It parses the given data and creates a tree-like structure that represents the structure of the message. */ +/* + KDE5: + * Do not convert singlepart <-> multipart automatically. + * A bunch of methods probably don't need to be virtual (since they're not needed + in either Message or NewsArticle). +*/ class KMIME_EXPORT Content { public: + /** + Describes a list of Content objects. + */ typedef QList List; /** Creates an empty Content object. */ Content(); /** Creates an empty Content object with a specified parent. @param parent the parent Content object @since 4.3 */ - explicit Content( Content* parent ); //TODO: Merge with the above + explicit Content( Content* parent ); // KDE5: Merge with the above. /** Creates a Content object containing the given raw data. @param head is a QByteArray containing the header data. @param body is a QByteArray containing the body data. */ Content( const QByteArray &head, const QByteArray &body ); /** Creates a Content object containing the given raw data. @param head is a QByteArray containing the header data. @param body is a QByteArray containing the body data. @param parent the parent Content object @since 4.3 */ - Content( const QByteArray &head, const QByteArray &body, Content *parent ); //TODO: merge with the above + // KDE5: Merge with the above. + Content( const QByteArray &head, const QByteArray &body, Content *parent ); /** Destroys this Content object. */ virtual ~Content(); /** Returns true if this Content object is not empty. */ bool hasContent() const; /** Sets the Content to the given raw data, containing the Content head and body separated by two linefeeds. @param l is a line-splitted list of the raw Content data. */ void setContent( const QList &l ); /** Sets the Content to the given raw data, containing the Content head and body separated by two linefeeds. @param s is a QByteArray containing the raw Content data. */ void setContent( const QByteArray &s ); /** - Parses the Contents, splitting into multiple sub-Contents. + Parses the Content. + This means the broken-down object representation of the Content is + updated from the string representation of the Content. + Call this if you want to access or change headers or sub-Contents. */ virtual void parse(); /** - Call to generate the MIME structure of the message. + Returns whether this Content is frozen. + A frozen content is immutable, i.e. calling assemble() will never modify + its head or body, and encodedContent() will return the same data before + and after parsing. + + @since 4.4. + @see setFrozen(). + */ + bool isFrozen() const; + + /** + Freezes this Content if @p frozen is true; otherwise unfreezes it. + + @since 4.4 + @see isFrozen(). + */ + void setFrozen( bool frozen = true ); + + /** + Generates the MIME message. + This means the string representation of this Content is updated from the + broken-down object representation. + Call this if you have made changes to the message, and want + encodedContent() to reflect those changes. + + @note assemble() has no effect if the Content isFrozen(). You may want + to freeze, for instance, signed sub-Contents, to make sure they are kept + unmodified. + + @warning assemble() may change the order of the headers, and other + details such as where folding occurs. This may break things like + signature verification, so you should *ONLY* call assemble() when you + have actually modified the message. */ virtual void assemble(); /** Clears the complete message and deletes all sub-Contents. */ + // KDE5: make non-virtual. virtual void clear(); /** Removes all sub-Contents from this message. Deletes them if @p del is true. This is different from calling removeContent() on each sub-Content, because removeContent() will convert this to a single-part Content if only one sub-Content is left. Calling clearContents() does NOT make this Content single-part. @param del Whether to delete the sub-Contents. @see removeContent() @since 4.4 */ void clearContents( bool del = true ); /** Returns the Content header raw data. @see setHead(). */ QByteArray head() const; /** Sets the Content header raw data. @param head is a QByteArray containing the header data. @see head(). */ void setHead( const QByteArray &head ); /** Extracts and removes the next header from @p head. The caller is responsible for deleting the returned header. @deprecated Use nextHeader( QByteArray ) @param head is a QByteArray containing the header data. */ - KDE_DEPRECATED Headers::Generic *getNextHeader( QByteArray &head ); /** Extracts and removes the next header from @p head. The caller is responsible for deleting the returned header. @since 4.2 + @deprecated Use KMime::extractFirstHeader(). @param head is a QByteArray containing the header data. */ - Headers::Generic *nextHeader( QByteArray &head ); + // KDE5: Remove this. This method has nothing to do with *this object. + KDE_DEPRECATED Headers::Generic *nextHeader( QByteArray &head ); /** Tries to find a @p type header in the message and returns it. @deprecated Use headerByType( const char * ) */ + // KDE5: Make non-virtual. KDE_DEPRECATED virtual Headers::Base *getHeaderByType( const char *type ); /** - Tries to find a @p type header in the message and returns it. + Returns the first header of type @p type, if it exists. Otherwise returns 0. + Note that the returned header may be empty. @since 4.2 */ + // KDE5: Make non-virtual. virtual Headers::Base *headerByType( const char *type ); + /** + Returns the first header of type T, if it exists. + If the header does not exist and @p create is true, creates an empty header + and returns it. Otherwise returns 0. + Note that the returned header may be empty. + @param create Whether to create the header if it does not exist. + @since 4.4. + */ + template T *header( bool create = false ); + /** Tries to find all the @p type headers in the message and returns it. Take care that this result is not cached, so could be slow. @since 4.2 */ virtual QList headersByType( const char *type ); + /** + Sets the specified header to this Content. + Any previous header of the same type is removed. + If you need multiple headers of the same type, use appendHeader() or + prependHeader(). + + @param h The header to set. + @see appendHeader() + @see removeHeader() + @since 4.4 + */ + // KDE5: make non-virtual. virtual void setHeader( Headers::Base *h ); + /** + Appends the specified header to the headers of this Content. + @param h The header to append. + @since 4.4 + */ + void appendHeader( Headers::Base *h ); + + /** + Prepends the specified header to the headers of this Content. + @param h The header to prepend. + @since 4.4 + */ + void prependHeader( Headers::Base *h ); + + /** + Searches for the first header of type @p type, and deletes it, removing + it from this Content. + @param type The type of the header to look for. + @return true if a header was found and removed. + */ + // TODO probably provide removeHeader() too. + // KDE5: make non-virtual. virtual bool removeHeader( const char *type ); + /** + @return true if this Content has a header of type @p type. + @param type The type of the header to look for. + */ + // TODO probably provide hasHeader() too. bool hasHeader( const char *type ); /** Returns the Content type header. @param create if true, create the header if it doesn't exist yet. */ Headers::ContentType *contentType( bool create=true ); /** Returns the Content transfer encoding. @param create if true, create the header if it doesn't exist yet. */ Headers::ContentTransferEncoding *contentTransferEncoding( bool create=true ); /** Returns the Content disposition. @param create if true, create the header if it doesn't exist yet. */ Headers::ContentDisposition *contentDisposition( bool create=true ); /** Returns the Content description. @param create if true, create the header if it doesn't exist yet. */ Headers::ContentDescription *contentDescription( bool create=true ); /** Returns the Content location. @param create if true, create the header if it doesn't exist yet. @since 4.2 */ Headers::ContentLocation *contentLocation( bool create=true ); + /** + Returns the Content ID. + @param create If true, create the header if it does not exist yet. + @since 4.4 + */ + Headers::ContentID *contentID( bool create = true ); /** Returns the size of the Content body after encoding. + (If the encoding is quoted-printable, this is only an approximate size.) */ int size(); /** Returns the size of this Content and all sub-Contents. */ int storageSize() const; /** Line count of this Content and all sub-Contents. */ int lineCount() const; /** Returns the Content body raw data. @see setBody(). */ QByteArray body() const; /** Sets the Content body raw data. @param body is a QByteArray containing the body data. @see body(). */ void setBody( const QByteArray &body ); /** Returns a QByteArray containing the encoded Content, including the Content header and all sub-Contents. @param useCrLf if true, use @ref CRLF instead of @ref LF for linefeeds. */ QByteArray encodedContent( bool useCrLf = false ); /** Returns the decoded Content body. */ QByteArray decodedContent(); /** Returns the decoded text. Additional to decodedContent(), this also applies charset decoding. If this is not a text Content, decodedText() returns an empty QString. @param trimText if true, then the decoded text will have all trailing whitespace removed. @param removeTrailingNewlines if true, then the decoded text will have all consecutive trailing newlines removed. The last trailing new line of the decoded text is always removed. */ QString decodedText( bool trimText = false, bool removeTrailingNewlines = false ); /** Sets the Content body to the given string using the current charset. @param s Unicode-encoded string. */ void fromUnicodeString( const QString &s ); /** Returns the first Content with mimetype text/. */ Content *textContent(); /** Returns a list of attachments. @param incAlternatives if true, include multipart/alternative parts. */ List attachments( bool incAlternatives = false ); /** Returns a list of sub-Contents. */ List contents() const; /** - Adds a new sub-Content, the current Content object is converted into a - multipart/mixed Content node if it has been a single-part Content. If the sub-Content - is already in another Content object, it is removed from there and its parent is - updated. + Adds a new sub-Content. If the sub-Content is already part of another + Content object, it is removed from there and its parent is updated. + If the current Content object is single-part, it is converted to + multipart/mixed first. + + @warning If the single-part to multipart conversion happens, all + pointers you may have into this object (such as headers) will become + invalid! - @param c The new sub-Content. - @param prepend if true, prepend to the Content list; else append + @param content The new sub-Content. + @param prepend If true, prepend to the Content list; otherwise append. to the Content list. @see removeContent(). */ - void addContent( Content *c, bool prepend = false ); + // KDE5: Do not convert single-part->multipart automatically. + void addContent( Content *content, bool prepend = false ); /** - Removes the given sub-Content. The current Content object is converted - into a single-part Content if only one sub-Content is left. + Removes the given sub-Content. If only one sub-Content is left, the + current Content object is converted into a single-part Content. - @param c The Content to remove. - @param del if true, delete the removed Content object. Otherwise its parent is set to NULL. + @warning If the multipart to single-part conversion happens, the head + and body of the single remaining sub-Content are copied over, and the + sub-Content is deleted. All pointers to it or into it (such as headers) + will become invalid! + + @param content The Content to remove. + @param del If true, delete the removed Content object. Otherwise set its + parent to 0. @see addContent(). @see clearContents(). */ - void removeContent( Content *c, bool del = false ); + // KDE5: Do not convert multipart->single-part automatically. + void removeContent( Content *content, bool del = false ); + /** + Changes the encoding of this Content to @p e. If the Content is binary, + this actually re-encodes the data to use the new encoding. + + @param e The new encoding to use. + */ void changeEncoding( Headers::contentEncoding e ); /** Saves the encoded Content to the given textstream @param ts is the stream where the Content should be written to. @param scrambleFromLines: if true, replace "\nFrom " with "\n>From " in the stream. This is needed to avoid problem with mbox-files */ void toStream( QTextStream &ts, bool scrambleFromLines = false ); + // NOTE: The charset methods below are accessed by the headers which + // have this Content as a parent. + /** Returns the charset that is used for all headers and the body if the charset is not declared explictly. @see setDefaultCharset() */ QByteArray defaultCharset() const; /** Sets the default charset. @param cs is a QByteArray containing the new default charset. @see defaultCharset(). */ void setDefaultCharset( const QByteArray &cs ); /** Use the default charset even if a different charset is declared in the article. @see setForceDefaultCharset(). */ bool forceDefaultCharset() const; /** Enables/disables the force mode, housekeeping. works correctly only when the article is completely empty or completely loaded. @param b if true, force the default charset to be used. @see forceDefaultCharset(). */ virtual void setForceDefaultCharset( bool b ); /** Returns the Content specified by the given index. - If the index doesn't point to a Content, 0 is returned, if the index + If the index does not point to a Content, 0 is returned. If the index is invalid (empty), this Content is returned. - @param index the Content index + @param index The Content index. */ Content *content( const ContentIndex &index ) const; /** - Returns the ContentIndex for the given Content, an invalid index - if the Content is not found withing the hierarchy. + Returns the ContentIndex for the given Content, or an invalid index + if the Content is not found within the hierarchy. @param content the Content object to search. */ ContentIndex indexForContent( Content *content ) const; /** - Returns true if this is the top-level node in the MIME tree, ie. if this + Returns true if this is the top-level node in the MIME tree, i.e. if this is actually a message or news article. */ virtual bool isTopLevel() const; /** * Sets a new parent to the Content and add to its contents list. If it already had a parent, it is removed from the * old parents contents list. * @param parent the new parent * @since 4.3 */ void setParent( Content *parent ); /** * Returns the parent content object, or NULL if the content doesn't have a parent. * @since 4.3 */ Content* parent() const; /** * Returns the toplevel content object, NULL if there is no such object. * @since 4.3 */ Content* topLevel() const; /** * Returns the index of this Content based on the topLevel() object. * @since 4.3 */ ContentIndex index() const; protected: /** Reimplement this method if you need to assemble additional headers in a derived class. Don't forget to call the implementation of the base class. @return The raw, assembled headers. */ virtual QByteArray assembleHeaders(); - QByteArray rawHeader( const char *name ) const; - QList rawHeaders( const char *name ) const; + /** + Returns the raw string representing the header of type @p name. + @deprecated Use KMime::extractHeader() directly instead. + */ + KDE_DEPRECATED QByteArray rawHeader( const char *name ) const; + + /** + Returns a list of raw strings representing all header of type @p name. + @deprecated Use KMime::extractHeaders() directly instead. + */ + KDE_DEPRECATED QList rawHeaders( const char *name ) const; + + /** + Returns whether this object holds text content. + */ + // KDE5: Not needed outside. Move to Private class. bool decodeText(); - template T *headerInstance( T *ptr, bool create ); + /** + @deprecated Use header() instead. + */ + template KDE_DEPRECATED T *headerInstance( T *ptr, bool create ); + + // KDE5: Move to Private class. Headers::Base::List h_eaders; //@cond PRIVATE ContentPrivate *d_ptr; explicit Content( ContentPrivate *d ); //@endcond private: Q_DECLARE_PRIVATE( Content ) Q_DISABLE_COPY( Content ) }; // some compilers (for instance Compaq C++) need template inline functions // here rather than in the *.cpp file template T *Content::headerInstance( T *ptr, bool create ) { - T dummy; //needed to access virtual member T::type() + return header( create ); +} - ptr=static_cast ( headerByType( dummy.type() ) ); - if ( !ptr && create ) { //no such header found, but we need one => create it - ptr = new T( this ); - h_eaders.append( ptr ); +template T *Content::header( bool create ) +{ + T dummy; + Headers::Base *h = headerByType( dummy.type() ); + if( h ) { + // Make sure the header is actually of the right type. + Q_ASSERT( dynamic_cast( h ) ); + } else if( create ) { + h = new T( this ); + setHeader( h ); } - - return ptr; + return static_cast( h ); } } // namespace KMime #endif // __KMIME_CONTENT_H__ diff --git a/kmime/kmime_content_p.h b/kmime/kmime_content_p.h index f6483ea01..c97ab605c 100644 --- a/kmime/kmime_content_p.h +++ b/kmime/kmime_content_p.h @@ -1,59 +1,64 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_CONTENT_P_H #define KMIME_CONTENT_P_H //@cond PRIVATE namespace KMime { class ContentPrivate { public: - ContentPrivate( Content *q ) : forceDefaultCS( false ), parent( 0 ), q_ptr( q ) + ContentPrivate( Content *q ) + : forceDefaultCS( false ), parent( 0 ), frozen( false ) + , q_ptr( q ) { defaultCS = KMime::cachedCharset( "ISO-8859-1" ); } virtual ~ContentPrivate() { qDeleteAll( contents ); contents.clear(); } - /** Returns the reconstructed content (header + body) for the content and sub-contents */ - QByteArray fullContent() const; + bool parseUuencoded(); + bool parseYenc(); + bool parseMultipart(); QByteArray head; QByteArray body; + QByteArray frozenBody; Content::List contents; QByteArray defaultCS; bool forceDefaultCS; Content *parent; + bool frozen; Content* q_ptr; Q_DECLARE_PUBLIC( Content ) }; } //@endcond #endif diff --git a/kmime/kmime_header_parsing.cpp b/kmime/kmime_header_parsing.cpp index 5b292062f..6af61a7ed 100644 --- a/kmime/kmime_header_parsing.cpp +++ b/kmime/kmime_header_parsing.cpp @@ -1,2038 +1,2101 @@ /* -*- c++ -*- kmime_header_parsing.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmime_header_parsing.h" #include "kmime_codecs.h" +#include "kmime_headerfactory.h" +#include "kmime_headers.h" #include "kmime_util.h" #include "kmime_dateformatter.h" #include "kmime_warning.h" #include #include #include #include #include #include #include // for isdigit #include using namespace KMime; using namespace KMime::Types; namespace KMime { namespace Types { // QUrl::fromAce is extremely expensive, so only use it when necessary. // Fortunately, the presence of IDNA is readily detected with a substring match... static inline QString QUrl_fromAce_wrapper( const QString & domain ) { if ( domain.contains( QLatin1String( "xn--" ) ) ) return QUrl::fromAce( domain.toLatin1() ); else return domain; } static QString addr_spec_as_string( const AddrSpec & as, bool pretty ) { if ( as.isEmpty() ) { return QString(); } bool needsQuotes = false; QString result; result.reserve( as.localPart.length() + as.domain.length() + 1 ); for ( int i = 0 ; i < as.localPart.length() ; ++i ) { const char ch = as.localPart[i].toLatin1(); if ( ch == '.' || isAText( ch ) ) { result += ch; } else { needsQuotes = true; if ( ch == '\\' || ch == '"' ) { result += '\\'; } result += ch; } } const QString dom = pretty ? QUrl_fromAce_wrapper( as.domain ) : as.domain ; if ( needsQuotes ) { return '"' + result + "\"@" + dom; } else { return result + '@' + dom; } } QString AddrSpec::asString() const { return addr_spec_as_string( *this, false ); } QString AddrSpec::asPrettyString() const { return addr_spec_as_string( *this, true ); } bool AddrSpec::isEmpty() const { return localPart.isEmpty() && domain.isEmpty(); } QByteArray Mailbox::address() const { return mAddrSpec.asString().toLatin1(); } AddrSpec Mailbox::addrSpec() const { return mAddrSpec; } QString Mailbox::name() const { return mDisplayName; } void Mailbox::setAddress( const AddrSpec &addr ) { mAddrSpec = addr; } void Mailbox::setAddress( const QByteArray &addr ) { const char *cursor = addr.constData(); if ( !HeaderParsing::parseAngleAddr( cursor, cursor + addr.length(), mAddrSpec ) ) { if ( !HeaderParsing::parseAddrSpec( cursor, cursor + addr.length(), mAddrSpec ) ) { kWarning() << "Invalid address"; return; } } } void Mailbox::setName( const QString &name ) { mDisplayName = name; } void Mailbox::setNameFrom7Bit( const QByteArray &name, const QByteArray &defaultCharset ) { QByteArray cs; mDisplayName = decodeRFC2047String( name, cs, defaultCharset, false ); } bool Mailbox::hasAddress() const { return !mAddrSpec.isEmpty(); } bool Mailbox::hasName() const { return !mDisplayName.isEmpty(); } QString Mailbox::prettyAddress() const { if ( !hasName() ) { return address(); } QString s = name(); if ( hasAddress() ) { s += QLatin1String(" <") + address() + QLatin1Char('>'); } return s; } void Mailbox::fromUnicodeString( const QString &s ) { from7BitString( encodeRFC2047String( s, "utf-8", false ) ); } void Mailbox::from7BitString( const QByteArray &s ) { const char *cursor = s.constData(); HeaderParsing::parseMailbox( cursor, cursor + s.length(), *this ); } QByteArray KMime::Types::Mailbox::as7BitString( const QByteArray &encCharset ) const { if ( !hasName() ) { return address(); } QByteArray rv; if ( isUsAscii( name() ) ) { QByteArray tmp = name().toLatin1(); addQuotes( tmp, false ); rv += tmp; } else { rv += encodeRFC2047String( name(), encCharset, true ); } if ( hasAddress() ) { rv += " <" + address() + '>'; } return rv; } } // namespace Types namespace HeaderParsing { // parse the encoded-word (scursor points to after the initial '=') bool parseEncodedWord( const char* &scursor, const char * const send, QString &result, QByteArray &language, QByteArray &usedCS, const QByteArray &defaultCS, bool forceCS ) { // make sure the caller already did a bit of the work. assert( *(scursor-1) == '=' ); // // STEP 1: // scan for the charset/language portion of the encoded-word // char ch = *scursor++; if ( ch != '?' ) { // kDebug(5320) << "first"; KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // remember start of charset (ie. just after the initial "=?") and // language (just after the first '*') fields: const char * charsetStart = scursor; const char * languageStart = 0; // find delimiting '?' (and the '*' separating charset and language // tags, if any): for ( ; scursor != send ; scursor++ ) { if ( *scursor == '?') { break; } else if ( *scursor == '*' && languageStart == 0 ) { languageStart = scursor + 1; } } // not found? can't be an encoded-word! if ( scursor == send || *scursor != '?' ) { // kDebug(5320) << "second"; KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // extract the language information, if any (if languageStart is 0, // language will be null, too): QByteArray maybeLanguage( languageStart, scursor - languageStart ); // extract charset information (keep in mind: the size given to the // ctor is one off due to the \0 terminator): QByteArray maybeCharset( charsetStart, ( languageStart ? languageStart - 1 : scursor ) - charsetStart ); // // STEP 2: // scan for the encoding portion of the encoded-word // // remember start of encoding (just _after_ the second '?'): scursor++; const char * encodingStart = scursor; // find next '?' (ending the encoding tag): for ( ; scursor != send ; scursor++ ) { if ( *scursor == '?' ) { break; } } // not found? Can't be an encoded-word! if ( scursor == send || *scursor != '?' ) { // kDebug(5320) << "third"; KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // extract the encoding information: QByteArray maybeEncoding( encodingStart, scursor - encodingStart ); // kDebug(5320) << "parseEncodedWord: found charset == \"" << maybeCharset // << "\"; language == \"" << maybeLanguage // << "\"; encoding == \"" << maybeEncoding << "\""; // // STEP 3: // scan for encoded-text portion of encoded-word // // remember start of encoded-text (just after the third '?'): scursor++; const char * encodedTextStart = scursor; // find the '?=' sequence (ending the encoded-text): for ( ; scursor != send ; scursor++ ) { if ( *scursor == '?' ) { if ( scursor + 1 != send ) { if ( *( scursor + 1 ) != '=' ) { // We expect a '=' after the '?', but we got something else; ignore KMIME_WARN << "Stray '?' in q-encoded word, ignoring this."; continue; } else { // yep, found a '?=' sequence scursor += 2; break; } } else { // The '?' is the last char, but we need a '=' after it! KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } } } if ( *( scursor - 2 ) != '?' || *( scursor - 1 ) != '=' || scursor < encodedTextStart + 2 ) { KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // set end sentinel for encoded-text: const char * const encodedTextEnd = scursor - 2; // // STEP 4: // setup decoders for the transfer encoding and the charset // // try if there's a codec for the encoding found: Codec * codec = Codec::codecForName( maybeEncoding ); if ( !codec ) { KMIME_WARN_UNKNOWN( Encoding, maybeEncoding ); return false; } // get an instance of a corresponding decoder: Decoder * dec = codec->makeDecoder(); assert( dec ); // try if there's a (text)codec for the charset found: bool matchOK = false; QTextCodec *textCodec = 0; if ( forceCS || maybeCharset.isEmpty() ) { textCodec = KGlobal::charsets()->codecForName( defaultCS, matchOK ); usedCS = cachedCharset( defaultCS ); } else { textCodec = KGlobal::charsets()->codecForName( maybeCharset, matchOK ); if ( !matchOK ) { //no suitable codec found => use default charset textCodec = KGlobal::charsets()->codecForName( defaultCS, matchOK ); usedCS = cachedCharset( defaultCS ); } else { usedCS = cachedCharset( maybeCharset ); } } if ( !matchOK || !textCodec ) { KMIME_WARN_UNKNOWN( Charset, maybeCharset ); delete dec; return false; }; // kDebug(5320) << "mimeName(): \"" << textCodec->name() << "\""; // allocate a temporary buffer to store the 8bit text: int encodedTextLength = encodedTextEnd - encodedTextStart; QByteArray buffer; buffer.resize( codec->maxDecodedSizeFor( encodedTextLength ) ); QByteArray::Iterator bit = buffer.begin(); QByteArray::ConstIterator bend = buffer.end(); // // STEP 5: // do the actual decoding // if ( !dec->decode( encodedTextStart, encodedTextEnd, bit, bend ) ) { KMIME_WARN << codec->name() << "codec lies about its maxDecodedSizeFor(" << encodedTextLength << ")\nresult may be truncated"; } result = textCodec->toUnicode( buffer.begin(), bit - buffer.begin() ); // kDebug(5320) << "result now: \"" << result << "\""; // cleanup: delete dec; language = maybeLanguage; return true; } static inline void eatWhiteSpace( const char* &scursor, const char * const send ) { while ( scursor != send && ( *scursor == ' ' || *scursor == '\n' || *scursor == '\t' || *scursor == '\r' ) ) scursor++; } bool parseAtom( const char * &scursor, const char * const send, QString &result, bool allow8Bit ) { QPair maybeResult; if ( parseAtom( scursor, send, maybeResult, allow8Bit ) ) { result += QString::fromLatin1( maybeResult.first, maybeResult.second ); return true; } return false; } bool parseAtom( const char * &scursor, const char * const send, QPair &result, bool allow8Bit ) { bool success = false; const char *start = scursor; while ( scursor != send ) { signed char ch = *scursor++; if ( ch > 0 && isAText( ch ) ) { // AText: OK success = true; } else if ( allow8Bit && ch < 0 ) { // 8bit char: not OK, but be tolerant. KMIME_WARN_8BIT( ch ); success = true; } else { // CTL or special - marking the end of the atom: // re-set sursor to point to the offending // char and return: scursor--; break; } } result.first = start; result.second = scursor - start; return success; } bool parseToken( const char * &scursor, const char * const send, QString &result, bool allow8Bit ) { QPair maybeResult; if ( parseToken( scursor, send, maybeResult, allow8Bit ) ) { result += QString::fromLatin1( maybeResult.first, maybeResult.second ); return true; } return false; } bool parseToken( const char * &scursor, const char * const send, QPair &result, bool allow8Bit ) { bool success = false; const char * start = scursor; while ( scursor != send ) { signed char ch = *scursor++; if ( ch > 0 && isTText( ch ) ) { // TText: OK success = true; } else if ( allow8Bit && ch < 0 ) { // 8bit char: not OK, but be tolerant. KMIME_WARN_8BIT( ch ); success = true; } else { // CTL or tspecial - marking the end of the atom: // re-set sursor to point to the offending // char and return: scursor--; break; } } result.first = start; result.second = scursor - start; return success; } #define READ_ch_OR_FAIL if ( scursor == send ) { \ KMIME_WARN_PREMATURE_END_OF( GenericQuotedString ); \ return false; \ } else { \ ch = *scursor++; \ } // known issues: // // - doesn't handle quoted CRLF bool parseGenericQuotedString( const char* &scursor, const char * const send, QString &result, bool isCRLF, const char openChar, const char closeChar ) { char ch; // We are in a quoted-string or domain-literal or comment and the // cursor points to the first char after the openChar. // We will apply unfolding and quoted-pair removal. // We return when we either encounter the end or unescaped openChar // or closeChar. assert( *(scursor-1) == openChar || *(scursor-1) == closeChar ); while ( scursor != send ) { ch = *scursor++; if ( ch == closeChar || ch == openChar ) { // end of quoted-string or another opening char: // let caller decide what to do. return true; } switch( ch ) { case '\\': // quoted-pair // misses "\" CRLF LWSP-char handling, see rfc822, 3.4.5 READ_ch_OR_FAIL; KMIME_WARN_IF_8BIT( ch ); result += QChar( ch ); break; case '\r': // ### // The case of lonely '\r' is easy to solve, as they're // not part of Unix Line-ending conventions. // But I see a problem if we are given Unix-native // line-ending-mails, where we cannot determine anymore // whether a given '\n' was part of a CRLF or was occurring // on it's own. READ_ch_OR_FAIL; if ( ch != '\n' ) { // CR on it's own... KMIME_WARN_LONE( CR ); result += QChar('\r'); scursor--; // points to after the '\r' again } else { // CRLF encountered. // lookahead: check for folding READ_ch_OR_FAIL; if ( ch == ' ' || ch == '\t' ) { // correct folding; // position cursor behind the CRLF WSP (unfolding) // and add the WSP to the result result += QChar( ch ); } else { // this is the "shouldn't happen"-case. There is a CRLF // inside a quoted-string without it being part of FWS. // We take it verbatim. KMIME_WARN_NON_FOLDING( CRLF ); result += "\r\n"; // the cursor is decremented again, so's we need not // duplicate the whole switch here. "ch" could've been // everything (incl. openChar or closeChar). scursor--; } } break; case '\n': // Note: CRLF has been handled above already! // ### LF needs special treatment, depending on whether isCRLF // is true (we can be sure a lonely '\n' was meant this way) or // false ('\n' alone could have meant LF or CRLF in the original // message. This parser assumes CRLF iff the LF is followed by // either WSP (folding) or NULL (premature end of quoted-string; // Should be fixed, since NULL is allowed as per rfc822). READ_ch_OR_FAIL; if ( !isCRLF && ( ch == ' ' || ch == '\t' ) ) { // folding // correct folding result += QChar( ch ); } else { // non-folding KMIME_WARN_LONE( LF ); result += QChar('\n'); // pos is decremented, so's we need not duplicate the whole // switch here. ch could've been everything (incl. <">, "\"). scursor--; } break; default: KMIME_WARN_IF_8BIT( ch ); result += QChar( ch ); } } return false; } // known issues: // // - doesn't handle encoded-word inside comments. bool parseComment( const char* &scursor, const char * const send, QString &result, bool isCRLF, bool reallySave ) { int commentNestingDepth = 1; const char *afterLastClosingParenPos = 0; QString maybeCmnt; const char *oldscursor = scursor; assert( *(scursor-1) == '(' ); while ( commentNestingDepth ) { QString cmntPart; if ( parseGenericQuotedString( scursor, send, cmntPart, isCRLF, '(', ')' ) ) { assert( *(scursor-1) == ')' || *(scursor-1) == '(' ); // see the kdoc for above function for the possible conditions // we have to check: switch ( *(scursor-1) ) { case ')': if ( reallySave ) { // add the chunk that's now surely inside the comment. result += maybeCmnt; result += cmntPart; if ( commentNestingDepth > 1 ) { // don't add the outermost ')'... result += QChar(')'); } maybeCmnt.clear(); } afterLastClosingParenPos = scursor; --commentNestingDepth; break; case '(': if ( reallySave ) { // don't add to "result" yet, because we might find that we // are already outside the (broken) comment... maybeCmnt += cmntPart; maybeCmnt += QChar('('); } ++commentNestingDepth; break; default: assert( 0 ); } // switch } else { // !parseGenericQuotedString, ie. premature end if ( afterLastClosingParenPos ) { scursor = afterLastClosingParenPos; } else { scursor = oldscursor; } return false; } } // while return true; } // known issues: none. bool parsePhrase( const char* &scursor, const char * const send, QString &result, bool isCRLF ) { enum { None, Phrase, Atom, EncodedWord, QuotedString } found = None; QString tmp; QByteArray lang, charset; const char *successfullyParsed = 0; // only used by the encoded-word branch const char *oldscursor; // used to suppress whitespace between adjacent encoded-words // (rfc2047, 6.2): bool lastWasEncodedWord = false; while ( scursor != send ) { char ch = *scursor++; switch ( ch ) { case '.': // broken, but allow for intorop's sake if ( found == None ) { --scursor; return false; } else { if ( scursor != send && ( *scursor == ' ' || *scursor == '\t' ) ) { result += ". "; } else { result += '.'; } successfullyParsed = scursor; } break; case '"': // quoted-string tmp.clear(); if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) { successfullyParsed = scursor; assert( *(scursor-1) == '"' ); switch ( found ) { case None: found = QuotedString; break; case Phrase: case Atom: case EncodedWord: case QuotedString: found = Phrase; result += QChar(' '); // rfc822, 3.4.4 break; default: assert( 0 ); } lastWasEncodedWord = false; result += tmp; } else { // premature end of quoted string. // What to do? Return leading '"' as special? Return as quoted-string? // We do the latter if we already found something, else signal failure. if ( found == None ) { return false; } else { result += QChar(' '); // rfc822, 3.4.4 result += tmp; return true; } } break; case '(': // comment // parse it, but ignore content: tmp.clear(); if ( parseComment( scursor, send, tmp, isCRLF, false /*don't bother with the content*/ ) ) { successfullyParsed = scursor; lastWasEncodedWord = false; // strictly interpreting rfc2047, 6.2 } else { if ( found == None ) { return false; } else { scursor = successfullyParsed; return true; } } break; case '=': // encoded-word tmp.clear(); oldscursor = scursor; lang.clear(); charset.clear(); if ( parseEncodedWord( scursor, send, tmp, lang, charset ) ) { successfullyParsed = scursor; switch ( found ) { case None: found = EncodedWord; break; case Phrase: case EncodedWord: case Atom: case QuotedString: if ( !lastWasEncodedWord ) { result += QChar(' '); // rfc822, 3.4.4 } found = Phrase; break; default: assert( 0 ); } lastWasEncodedWord = true; result += tmp; break; } else { // parse as atom: scursor = oldscursor; } // fall though... default: //atom tmp.clear(); scursor--; if ( parseAtom( scursor, send, tmp, true /* allow 8bit */ ) ) { successfullyParsed = scursor; switch ( found ) { case None: found = Atom; break; case Phrase: case Atom: case EncodedWord: case QuotedString: found = Phrase; result += QChar(' '); // rfc822, 3.4.4 break; default: assert( 0 ); } lastWasEncodedWord = false; result += tmp; } else { if ( found == None ) { return false; } else { scursor = successfullyParsed; return true; } } } eatWhiteSpace( scursor, send ); } return found != None; } bool parseDotAtom( const char* &scursor, const char * const send, QString &result, bool isCRLF ) { eatCFWS( scursor, send, isCRLF ); // always points to just after the last atom parsed: const char *successfullyParsed; QString tmp; if ( !parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) { return false; } result += tmp; successfullyParsed = scursor; while ( scursor != send ) { // end of header or no '.' -> return if ( scursor == send || *scursor != '.' ) { return true; } scursor++; // eat '.' if ( scursor == send || !isAText( *scursor ) ) { // end of header or no AText, but this time following a '.'!: // reset cursor to just after last successfully parsed char and // return: scursor = successfullyParsed; return true; } // try to parse the next atom: QString maybeAtom; if ( !parseAtom( scursor, send, maybeAtom, false /*no 8bit*/ ) ) { scursor = successfullyParsed; return true; } result += QChar('.'); result += maybeAtom; successfullyParsed = scursor; } scursor = successfullyParsed; return true; } void eatCFWS( const char* &scursor, const char * const send, bool isCRLF ) { QString dummy; while ( scursor != send ) { const char *oldscursor = scursor; char ch = *scursor++; switch( ch ) { case ' ': case '\t': // whitespace case '\r': case '\n': // folding continue; case '(': // comment if ( parseComment( scursor, send, dummy, isCRLF, false /*don't save*/ ) ) { continue; } scursor = oldscursor; return; default: scursor = oldscursor; return; } } } bool parseDomain( const char* &scursor, const char * const send, QString &result, bool isCRLF ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // domain := dot-atom / domain-literal / atom *("." atom) // // equivalent to: // domain = dot-atom / domain-literal, // since parseDotAtom does allow CFWS between atoms and dots if ( *scursor == '[' ) { // domain-literal: QString maybeDomainLiteral; // eat '[': scursor++; while ( parseGenericQuotedString( scursor, send, maybeDomainLiteral, isCRLF, '[', ']' ) ) { if ( scursor == send ) { // end of header: check for closing ']': if ( *(scursor-1) == ']' ) { // OK, last char was ']': result = maybeDomainLiteral; return true; } else { // not OK, domain-literal wasn't closed: return false; } } // we hit openChar in parseGenericQuotedString. // include it in maybeDomainLiteral and keep on parsing: if ( *(scursor-1) == '[' ) { maybeDomainLiteral += QChar('['); continue; } // OK, real end of domain-literal: result = maybeDomainLiteral; return true; } } else { // dot-atom: QString maybeDotAtom; if ( parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) { result = maybeDotAtom; return true; } } return false; } bool parseObsRoute( const char* &scursor, const char* const send, QStringList &result, bool isCRLF, bool save ) { while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // empty entry: if ( *scursor == ',' ) { scursor++; if ( save ) { result.append( QString() ); } continue; } // empty entry ending the list: if ( *scursor == ':' ) { scursor++; if ( save ) { result.append( QString() ); } return true; } // each non-empty entry must begin with '@': if ( *scursor != '@' ) { return false; } else { scursor++; } QString maybeDomain; if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) { return false; } if ( save ) { result.append( maybeDomain ); } // eat the following (optional) comma: eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( *scursor == ':' ) { scursor++; return true; } if ( *scursor == ',' ) { scursor++; } } return false; } bool parseAddrSpec( const char* &scursor, const char * const send, AddrSpec &result, bool isCRLF ) { // // STEP 1: // local-part := dot-atom / quoted-string / word *("." word) // // this is equivalent to: // local-part := word *("." word) QString maybeLocalPart; QString tmp; while ( scursor != send ) { // first, eat any whitespace eatCFWS( scursor, send, isCRLF ); char ch = *scursor++; switch ( ch ) { case '.': // dot maybeLocalPart += QChar('.'); break; case '@': goto SAW_AT_SIGN; break; case '"': // quoted-string tmp.clear(); if ( parseGenericQuotedString( scursor, send, tmp, isCRLF, '"', '"' ) ) { maybeLocalPart += tmp; } else { return false; } break; default: // atom scursor--; // re-set scursor to point to ch again tmp.clear(); if ( parseAtom( scursor, send, tmp, false /* no 8bit */ ) ) { maybeLocalPart += tmp; } else { return false; // parseAtom can only fail if the first char is non-atext. } break; } } return false; // // STEP 2: // domain // SAW_AT_SIGN: assert( *(scursor-1) == '@' ); QString maybeDomain; if ( !parseDomain( scursor, send, maybeDomain, isCRLF ) ) { return false; } result.localPart = maybeLocalPart; result.domain = maybeDomain; return true; } bool parseAngleAddr( const char* &scursor, const char * const send, AddrSpec &result, bool isCRLF ) { // first, we need an opening angle bracket: eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '<' ) { return false; } scursor++; // eat '<' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( *scursor == '@' || *scursor == ',' ) { // obs-route: parse, but ignore: KMIME_WARN << "obsolete source route found! ignoring."; QStringList dummy; if ( !parseObsRoute( scursor, send, dummy, isCRLF, false /* don't save */ ) ) { return false; } // angle-addr isn't complete until after the '>': if ( scursor == send ) { return false; } } // parse addr-spec: AddrSpec maybeAddrSpec; if ( !parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '>' ) { return false; } scursor++; result = maybeAddrSpec; return true; } bool parseMailbox( const char* &scursor, const char * const send, Mailbox &result, bool isCRLF ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } AddrSpec maybeAddrSpec; QString maybeDisplayName; // first, try if it's a vanilla addr-spec: const char * oldscursor = scursor; if ( parseAddrSpec( scursor, send, maybeAddrSpec, isCRLF ) ) { result.setAddress( maybeAddrSpec ); // check for the obsolete form of display-name (as comment): eatWhiteSpace( scursor, send ); if ( scursor != send && *scursor == '(' ) { scursor++; if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) { return false; } } result.setNameFrom7Bit( maybeDisplayName.toLatin1() ); return true; } scursor = oldscursor; // second, see if there's a display-name: if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) { // failed: reset cursor, note absent display-name maybeDisplayName.clear(); scursor = oldscursor; } else { // succeeded: eat CFWS eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } } // third, parse the angle-addr: if ( !parseAngleAddr( scursor, send, maybeAddrSpec, isCRLF ) ) { return false; } if ( maybeDisplayName.isNull() ) { // check for the obsolete form of display-name (as comment): eatWhiteSpace( scursor, send ); if ( scursor != send && *scursor == '(' ) { scursor++; if ( !parseComment( scursor, send, maybeDisplayName, isCRLF, true /*keep*/ ) ) { return false; } } } result.setName( maybeDisplayName ); result.setAddress( maybeAddrSpec ); return true; } bool parseGroup( const char* &scursor, const char * const send, Address &result, bool isCRLF ) { // group := display-name ":" [ mailbox-list / CFWS ] ";" [CFWS] // // equivalent to: // group := display-name ":" [ obs-mbox-list ] ";" eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // get display-name: QString maybeDisplayName; if ( !parsePhrase( scursor, send, maybeDisplayName, isCRLF ) ) { return false; } // get ":": eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != ':' ) { return false; } result.displayName = maybeDisplayName; // get obs-mbox-list (may contain empty entries): scursor++; while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // empty entry: if ( *scursor == ',' ) { scursor++; continue; } // empty entry ending the list: if ( *scursor == ';' ) { scursor++; return true; } Mailbox maybeMailbox; if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { return false; } result.mailboxList.append( maybeMailbox ); eatCFWS( scursor, send, isCRLF ); // premature end: if ( scursor == send ) { return false; } // regular end of the list: if ( *scursor == ';' ) { scursor++; return true; } // eat regular list entry separator: if ( *scursor == ',' ) { scursor++; } } return false; } bool parseAddress( const char* &scursor, const char * const send, Address &result, bool isCRLF ) { // address := mailbox / group eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // first try if it's a single mailbox: Mailbox maybeMailbox; const char * oldscursor = scursor; if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { // yes, it is: result.displayName.clear(); result.mailboxList.append( maybeMailbox ); return true; } scursor = oldscursor; Address maybeAddress; // no, it's not a single mailbox. Try if it's a group: if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) ) { return false; } result = maybeAddress; return true; } bool parseAddressList( const char* &scursor, const char * const send, AddressList &result, bool isCRLF ) { while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // end of header: this is OK. if ( scursor == send ) { return true; } // empty entry: ignore: if ( *scursor == ',' ) { scursor++; continue; } // broken clients might use ';' as list delimiter, accept that as well if ( *scursor == ';' ) { scursor++; continue; } // parse one entry Address maybeAddress; if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) { return false; } result.append( maybeAddress ); eatCFWS( scursor, send, isCRLF ); // end of header: this is OK. if ( scursor == send ) { return true; } // comma separating entries: eat it. if ( *scursor == ',' ) { scursor++; } } return true; } static QString asterisk = QString::fromLatin1( "*0*", 1 ); static QString asteriskZero = QString::fromLatin1( "*0*", 2 ); //static QString asteriskZeroAsterisk = QString::fromLatin1( "*0*", 3 ); bool parseParameter( const char* &scursor, const char * const send, QPair &result, bool isCRLF ) { // parameter = regular-parameter / extended-parameter // regular-parameter = regular-parameter-name "=" value // extended-parameter = // value = token / quoted-string // // note that rfc2231 handling is out of the scope of this function. // Therefore we return the attribute as QString and the value as // (start,length) tupel if we see that the value is encoded // (trailing asterisk), for parseParameterList to decode... eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // parse the parameter name: // QString maybeAttribute; if ( !parseToken( scursor, send, maybeAttribute, false /* no 8bit */ ) ) { return false; } eatCFWS( scursor, send, isCRLF ); // premature end: not OK (haven't seen '=' yet). if ( scursor == send || *scursor != '=' ) { return false; } scursor++; // eat '=' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { // don't choke on attribute=, meaning the value was omitted: if ( maybeAttribute.endsWith( asterisk ) ) { KMIME_WARN << "attribute ends with \"*\", but value is empty!" "Chopping away \"*\"."; maybeAttribute.truncate( maybeAttribute.length() - 1 ); } result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() ); return true; } const char * oldscursor = scursor; // // parse the parameter value: // QStringOrQPair maybeValue; if ( *scursor == '"' ) { // value is a quoted-string: scursor++; if ( maybeAttribute.endsWith( asterisk ) ) { // attributes ending with "*" designate extended-parameters, // which cannot have quoted-strings as values. So we remove the // trailing "*" to not confuse upper layers. KMIME_WARN << "attribute ends with \"*\", but value is a quoted-string!" "Chopping away \"*\"."; maybeAttribute.truncate( maybeAttribute.length() - 1 ); } if ( !parseGenericQuotedString( scursor, send, maybeValue.qstring, isCRLF ) ) { scursor = oldscursor; result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() ); return false; // this case needs further processing by upper layers!! } } else { // value is a token: if ( !parseToken( scursor, send, maybeValue.qpair, false /* no 8bit */ ) ) { scursor = oldscursor; result = qMakePair( maybeAttribute.toLower(), QStringOrQPair() ); return false; // this case needs further processing by upper layers!! } } result = qMakePair( maybeAttribute.toLower(), maybeValue ); return true; } bool parseRawParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF ) { // we use parseParameter() consecutively to obtain a map of raw // attributes to raw values. "Raw" here means that we don't do // rfc2231 decoding and concatenation. This is left to // parseParameterList(), which will call this function. // // The main reason for making this chunk of code a separate // (private) method is that we can deal with broken parameters // _here_ and leave the rfc2231 handling solely to // parseParameterList(), which will still be enough work. while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // empty entry ending the list: OK. if ( scursor == send ) { return true; } // empty list entry: ignore. if ( *scursor == ';' ) { scursor++; continue; } QPair maybeParameter; if ( !parseParameter( scursor, send, maybeParameter, isCRLF ) ) { // we need to do a bit of work if the attribute is not // NULL. These are the cases marked with "needs further // processing" in parseParameter(). Specifically, parsing of the // token or the quoted-string, which should represent the value, // failed. We take the easy way out and simply search for the // next ';' to start parsing again. (Another option would be to // take the text between '=' and ';' as value) if ( maybeParameter.first.isNull() ) { return false; } while ( scursor != send ) { if ( *scursor++ == ';' ) { goto IS_SEMICOLON; } } // scursor == send case: end of list. return true; IS_SEMICOLON: // *scursor == ';' case: parse next entry. continue; } // successful parsing brings us here: result.insert( maybeParameter.first, maybeParameter.second ); eatCFWS( scursor, send, isCRLF ); // end of header: ends list. if ( scursor == send ) { return true; } // regular separator: eat it. if ( *scursor == ';' ) { scursor++; } } return true; } static void decodeRFC2231Value( Codec* &rfc2231Codec, QTextCodec* &textcodec, bool isContinuation, QString &value, QPair &source ) { // // parse the raw value into (charset,language,text): // const char * decBegin = source.first; const char * decCursor = decBegin; const char * decEnd = decCursor + source.second; if ( !isContinuation ) { // find the first single quote while ( decCursor != decEnd ) { if ( *decCursor == '\'' ) { break; } else { decCursor++; } } if ( decCursor == decEnd ) { // there wasn't a single single quote at all! // take the whole value to be in latin-1: KMIME_WARN << "No charset in extended-initial-value." "Assuming \"iso-8859-1\"."; value += QString::fromLatin1( decBegin, source.second ); return; } QByteArray charset( decBegin, decCursor - decBegin ); const char * oldDecCursor = ++decCursor; // find the second single quote (we ignore the language tag): while ( decCursor != decEnd ) { if ( *decCursor == '\'' ) { break; } else { decCursor++; } } if ( decCursor == decEnd ) { KMIME_WARN << "No language in extended-initial-value." "Trying to recover."; decCursor = oldDecCursor; } else { decCursor++; } // decCursor now points to the start of the // "extended-other-values": // // get the decoders: // bool matchOK = false; textcodec = KGlobal::charsets()->codecForName( charset, matchOK ); if ( !matchOK ) { textcodec = 0; KMIME_WARN_UNKNOWN( Charset, charset ); } } if ( !rfc2231Codec ) { rfc2231Codec = Codec::codecForName("x-kmime-rfc2231"); assert( rfc2231Codec ); } if ( !textcodec ) { value += QString::fromLatin1( decCursor, decEnd - decCursor ); return; } Decoder * dec = rfc2231Codec->makeDecoder(); assert( dec ); // // do the decoding: // QByteArray buffer; buffer.resize( rfc2231Codec->maxDecodedSizeFor( decEnd - decCursor ) ); QByteArray::Iterator bit = buffer.begin(); QByteArray::ConstIterator bend = buffer.end(); if ( !dec->decode( decCursor, decEnd, bit, bend ) ) { KMIME_WARN << rfc2231Codec->name() << "codec lies about its maxDecodedSizeFor()" << endl << "result may be truncated"; } value += textcodec->toUnicode( buffer.begin(), bit - buffer.begin() ); // kDebug(5320) << "value now: \"" << value << "\""; // cleanup: delete dec; } // known issues: // - permutes rfc2231 continuations when the total number of parts // exceeds 10 (other-sections then becomes *xy, ie. two digits) bool parseParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF ) { // parse the list into raw attribute-value pairs: QMap rawParameterList; if (!parseRawParameterList( scursor, send, rawParameterList, isCRLF ) ) { return false; } if ( rawParameterList.isEmpty() ) { return true; } // decode rfc 2231 continuations and alternate charset encoding: // NOTE: this code assumes that what QMapIterator delivers is sorted // by the key! Codec * rfc2231Codec = 0; QTextCodec * textcodec = 0; QString attribute; QString value; enum Modes { NoMode = 0x0, Continued = 0x1, Encoded = 0x2 } mode; QMap::Iterator it, end = rawParameterList.end(); for ( it = rawParameterList.begin() ; it != end ; ++it ) { if ( attribute.isNull() || !it.key().startsWith( attribute ) ) { // // new attribute: // // store the last attribute/value pair in the result map now: if ( !attribute.isNull() ) { result.insert( attribute, value ); } // and extract the information from the new raw attribute: value.clear(); attribute = it.key(); mode = NoMode; // is the value encoded? if ( attribute.endsWith( asterisk ) ) { attribute.truncate( attribute.length() - 1 ); mode = (Modes) ((int) mode | Encoded); } // is the value continued? if ( attribute.endsWith( asteriskZero ) ) { attribute.truncate( attribute.length() - 2 ); mode = (Modes) ((int) mode | Continued); } // // decode if necessary: // if ( mode & Encoded ) { decodeRFC2231Value( rfc2231Codec, textcodec, false, /* isn't continuation */ value, (*it).qpair ); } else { // not encoded. if ( (*it).qpair.first ) { value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second ); } else { value += (*it).qstring; } } // // shortcut-processing when the value isn't encoded: // if ( !(mode & Continued) ) { // save result already: result.insert( attribute, value ); // force begin of a new attribute: attribute.clear(); } } else { // it.key().startsWith( attribute ) // // continuation // // ignore the section and trust QMap to have sorted the keys: if ( it.key().endsWith( asterisk ) ) { // encoded decodeRFC2231Value( rfc2231Codec, textcodec, true, /* is continuation */ value, (*it).qpair ); } else { // not encoded if ( (*it).qpair.first ) { value += QString::fromLatin1( (*it).qpair.first, (*it).qpair.second ); } else { value += (*it).qstring; } } } } // write last attr/value pair: if ( !attribute.isNull() ) { result.insert( attribute, value ); } return true; } static const char * const stdDayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const int stdDayNamesLen = sizeof stdDayNames / sizeof *stdDayNames; static bool parseDayName( const char* &scursor, const char * const send ) { // check bounds: if ( send - scursor < 3 ) { return false; } for ( int i = 0 ; i < stdDayNamesLen ; ++i ) { if ( qstrnicmp( scursor, stdDayNames[i], 3 ) == 0 ) { scursor += 3; // kDebug(5320) << "found" << stdDayNames[i]; return true; } } return false; } static const char * const stdMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const int stdMonthNamesLen = sizeof stdMonthNames / sizeof *stdMonthNames; static bool parseMonthName( const char* &scursor, const char * const send, int &result ) { // check bounds: if ( send - scursor < 3 ) { return false; } for ( result = 0 ; result < stdMonthNamesLen ; ++result ) { if ( qstrnicmp( scursor, stdMonthNames[result], 3 ) == 0 ) { scursor += 3; return true; } } // not found: return false; } static const struct { const char * tzName; long int secsEastOfGMT; } timeZones[] = { // rfc 822 timezones: { "GMT", 0 }, { "UT", 0 }, { "EDT", -4*3600 }, { "EST", -5*3600 }, { "MST", -5*3600 }, { "CST", -6*3600 }, { "MDT", -6*3600 }, { "MST", -7*3600 }, { "PDT", -7*3600 }, { "PST", -8*3600 }, // common, non-rfc-822 zones: { "CET", 1*3600 }, { "MET", 1*3600 }, { "UTC", 0 }, { "CEST", 2*3600 }, { "BST", 1*3600 }, // rfc 822 military timezones: { "Z", 0 }, { "A", -1*3600 }, { "B", -2*3600 }, { "C", -3*3600 }, { "D", -4*3600 }, { "E", -5*3600 }, { "F", -6*3600 }, { "G", -7*3600 }, { "H", -8*3600 }, { "I", -9*3600 }, // J is not used! { "K", -10*3600 }, { "L", -11*3600 }, { "M", -12*3600 }, { "N", 1*3600 }, { "O", 2*3600 }, { "P", 3*3600 }, { "Q", 4*3600 }, { "R", 5*3600 }, { "S", 6*3600 }, { "T", 7*3600 }, { "U", 8*3600 }, { "V", 9*3600 }, { "W", 10*3600 }, { "X", 11*3600 }, { "Y", 12*3600 }, }; static const int timeZonesLen = sizeof timeZones / sizeof *timeZones; static bool parseAlphaNumericTimeZone( const char* &scursor, const char * const send, long int &secsEastOfGMT, bool &timeZoneKnown ) { QPair maybeTimeZone( 0, 0 ); if ( !parseToken( scursor, send, maybeTimeZone, false /*no 8bit*/ ) ) { return false; } for ( int i = 0 ; i < timeZonesLen ; ++i ) { if ( qstrnicmp( timeZones[i].tzName, maybeTimeZone.first, maybeTimeZone.second ) == 0 ) { scursor += maybeTimeZone.second; secsEastOfGMT = timeZones[i].secsEastOfGMT; timeZoneKnown = true; return true; } } // don't choke just because we don't happen to know the time zone KMIME_WARN_UNKNOWN( time zone, QByteArray( maybeTimeZone.first, maybeTimeZone.second ) ); secsEastOfGMT = 0; timeZoneKnown = false; return true; } // parse a number and return the number of digits parsed: int parseDigits( const char* &scursor, const char * const send, int &result ) { result = 0; int digits = 0; for ( ; scursor != send && isdigit( *scursor ) ; scursor++, digits++ ) { result *= 10; result += int( *scursor - '0' ); } return digits; } static bool parseTimeOfDay( const char* &scursor, const char * const send, int &hour, int &min, int &sec, bool isCRLF=false ) { // time-of-day := 2DIGIT [CFWS] ":" [CFWS] 2DIGIT [ [CFWS] ":" 2DIGIT ] // // 2DIGIT representing "hour": // if ( !parseDigits( scursor, send, hour ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != ':' ) { return false; } scursor++; // eat ':' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // 2DIGIT representing "minute": // if ( !parseDigits( scursor, send, min ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return true; // seconds are optional } // // let's see if we have a 2DIGIT representing "second": // if ( *scursor == ':' ) { // yepp, there are seconds: scursor++; // eat ':' eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( !parseDigits( scursor, send, sec ) ) { return false; } } else { sec = 0; } return true; } bool parseTime( const char* &scursor, const char * send, int &hour, int &min, int &sec, long int &secsEastOfGMT, bool &timeZoneKnown, bool isCRLF ) { // time := time-of-day CFWS ( zone / obs-zone ) // // obs-zone := "UT" / "GMT" / // "EST" / "EDT" / ; -0500 / -0400 // "CST" / "CDT" / ; -0600 / -0500 // "MST" / "MDT" / ; -0700 / -0600 // "PST" / "PDT" / ; -0800 / -0700 // "A"-"I" / "a"-"i" / // "K"-"Z" / "k"-"z" eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } if ( !parseTimeOfDay( scursor, send, hour, min, sec, isCRLF ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { timeZoneKnown = false; secsEastOfGMT = 0; return true; // allow missing timezone } timeZoneKnown = true; if ( *scursor == '+' || *scursor == '-' ) { // remember and eat '-'/'+': const char sign = *scursor++; // numerical timezone: int maybeTimeZone; if ( parseDigits( scursor, send, maybeTimeZone ) != 4 ) { return false; } secsEastOfGMT = 60 * ( maybeTimeZone / 100 * 60 + maybeTimeZone % 100 ); if ( sign == '-' ) { secsEastOfGMT *= -1; if ( secsEastOfGMT == 0 ) { timeZoneKnown = false; // -0000 means indetermined tz } } } else { // maybe alphanumeric timezone: if ( !parseAlphaNumericTimeZone( scursor, send, secsEastOfGMT, timeZoneKnown ) ) { return false; } } return true; } bool parseDateTime( const char* &scursor, const char * const send, KDateTime &result, bool isCRLF ) { // Parsing date-time; strict mode: // // date-time := [ [CFWS] day-name [CFWS] "," ] ; wday // (expanded) [CFWS] 1*2DIGIT CFWS month-name CFWS 2*DIGIT [CFWS] ; date // time // // day-name := "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // month-name := "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" result = KDateTime(); QDateTime maybeDateTime; eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // let's see if there's a day-of-week: // if ( parseDayName( scursor, send ) ) { eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // day-name should be followed by ',' but we treat it as optional: if ( *scursor == ',' ) { scursor++; // eat ',' eatCFWS( scursor, send, isCRLF ); } } // // 1*2DIGIT representing "day" (of month): // int maybeDay; if ( !parseDigits( scursor, send, maybeDay ) ) { return false; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // month-name: // int maybeMonth = 0; if ( !parseMonthName( scursor, send, maybeMonth ) ) { return false; } if ( scursor == send ) { return false; } assert( maybeMonth >= 0 ); assert( maybeMonth <= 11 ); ++maybeMonth; // 0-11 -> 1-12 eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } // // 2*DIGIT representing "year": // int maybeYear; if ( !parseDigits( scursor, send, maybeYear ) ) { return false; } // RFC 2822 4.3 processing: if ( maybeYear < 50 ) { maybeYear += 2000; } else if ( maybeYear < 1000 ) { maybeYear += 1900; } // else keep as is if ( maybeYear < 1900 ) { return false; // rfc2822, 3.3 } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } maybeDateTime.setDate( QDate( maybeYear, maybeMonth, maybeDay ) ); // // time // int maybeHour, maybeMinute, maybeSecond; long int secsEastOfGMT; bool timeZoneKnown = true; if ( !parseTime( scursor, send, maybeHour, maybeMinute, maybeSecond, secsEastOfGMT, timeZoneKnown, isCRLF ) ) { return false; } maybeDateTime.setTime( QTime( maybeHour, maybeMinute, maybeSecond ) ); if ( !maybeDateTime.isValid() ) return false; result = KDateTime( maybeDateTime, KDateTime::Spec( KDateTime::OffsetFromUTC, secsEastOfGMT ) ); if ( !result.isValid() ) return false; return true; } +Headers::Base *extractFirstHeader( QByteArray &head ) +{ + int pos1=-1, pos2=0, len=head.length()-1; + bool folded( false ); + Headers::Base *header=0; + + pos1 = head.indexOf( ": " ); + + if ( pos1 > -1 ) { //there is another header + pos2 = pos1 += 2; //skip the name + + if ( head[pos2] != '\n' ) { // check if the header is not empty + while ( 1 ) { + pos2 = head.indexOf( '\n', pos2 + 1 ); + if ( pos2 == -1 || pos2 == len || + ( head[pos2+1] != ' ' && head[pos2+1] != '\t' ) ) { + //break if we reach the end of the string, honor folded lines + break; + } else { + folded = true; + } + } + } + + if ( pos2 < 0 ) { + pos2 = len + 1; //take the rest of the string + } + + QByteArray rawType = head.left( pos1 - 2 ); + QByteArray rawData = head.mid( pos1, pos2 - pos1 ); + if( folded ) { + rawData = unfoldHeader( rawData ); + } + header = HeaderFactory::self()->createHeader( rawType ); + if( !header ) { + kWarning() << "Returning Generic header of type" << rawType; + header = new Headers::Generic( rawType ); + } + header->from7BitString( rawData ); + + head.remove( 0, pos2 + 1 ); + } else { + head = ""; + } + + return header; +} + +Headers::Base::List parseHeaders( const QByteArray &head ) +{ + Headers::Base::List ret; + Headers::Base *h; + + QByteArray copy = head; + while( ( h = extractFirstHeader( copy ) ) ) { + ret << h; + } + + return ret; +} + } // namespace HeaderParsing } // namespace KMime diff --git a/kmime/kmime_header_parsing.h b/kmime/kmime_header_parsing.h index 084afc3f1..3dca2d1a8 100644 --- a/kmime/kmime_header_parsing.h +++ b/kmime/kmime_header_parsing.h @@ -1,335 +1,343 @@ /* -*- c++ -*- kmime_header_parsing.h KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KMIME_HEADER_PARSING_H__ #define __KMIME_HEADER_PARSING_H__ #include #include #include #include "kmime_export.h" template class QMap; class QStringList; namespace KMime { +namespace Headers { + class Base; +} + namespace Types { // for when we can't make up our mind what to use... struct KMIME_EXPORT QStringOrQPair { QStringOrQPair() : qstring(), qpair( 0, 0 ) {} QString qstring; QPair qpair; }; struct KMIME_EXPORT AddrSpec { QString asString() const; /*! This is the same as asString(), except it decodes IDNs for display */ QString asPrettyString() const; bool isEmpty() const; QString localPart; QString domain; }; typedef QList AddrSpecList; /** Represents an (email address, display name) pair according RFC 2822, section 3.4. */ class KMIME_EXPORT Mailbox { public: typedef QList List; /** Returns a string representation of the email address, without the angle brackets. */ QByteArray address() const; AddrSpec addrSpec() const; /** Returns the display name. */ QString name() const; /** Sets the email address. */ void setAddress( const AddrSpec &addr ); /** Sets the email address. */ void setAddress( const QByteArray &addr ); /** Sets the name. */ void setName( const QString &name ); /** Sets the name based on a 7bit encoded string. */ void setNameFrom7Bit( const QByteArray &name, const QByteArray &defaultCharset = QByteArray() ); /** Returns true if this mailbox has an address. */ bool hasAddress() const; /** Returns true if this mailbox has a display name. */ bool hasName() const; /** Returns a assembled display name / address string of the following form: "Display Name <address>". These are unicode strings without any transport encoding, ie. they are only suitable for displaying. */ QString prettyAddress() const; /** Parses the given unicode string. */ void fromUnicodeString( const QString &s ); /** Parses the given 7bit encoded string. */ void from7BitString( const QByteArray &s ); /** Returns a 7bit transport encoded representation of this mailbox. @param encCharset The charset used for encoding. */ QByteArray as7BitString( const QByteArray &encCharset ) const; private: QString mDisplayName; AddrSpec mAddrSpec; }; typedef QList MailboxList; struct KMIME_EXPORT Address { QString displayName; MailboxList mailboxList; }; typedef QList
AddressList; } // namespace KMime::Types namespace HeaderParsing { /** Parses the encoded word. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param result the decoded string the encoded work represented. @param language The language parameter according to RFC 2231, section 5. @param usedCS the used charset is returned here @param defaultCS the charset to use in case the detected one isn't known to us. @param forceCS force the use of the default charset. @return true if the input string was successfully decode; false otherwise. */ KMIME_EXPORT bool parseEncodedWord( const char* &scursor, const char * const send, QString &result, QByteArray &language, QByteArray &usedCS, const QByteArray &defaultCS = QByteArray(), bool forceCS = false ); // // The parsing squad: // /** You may or may not have already started parsing into the atom. This function will go on where you left off. */ KMIME_EXPORT bool parseAtom( const char* &scursor, const char * const send, QString &result, bool allow8Bit=false ); KMIME_EXPORT bool parseAtom( const char* &scursor, const char * const send, QPair &result, bool allow8Bit=false ); /** You may or may not have already started parsing into the token. This function will go on where you left off. */ KMIME_EXPORT bool parseToken( const char* &scursor, const char * const send, QString &result, bool allow8Bit=false ); KMIME_EXPORT bool parseToken( const char* &scursor, const char * const send, QPair &result, bool allow8Bit=false ); /** @p scursor must be positioned after the opening openChar. */ KMIME_EXPORT bool parseGenericQuotedString( const char* &scursor, const char* const send, QString &result, bool isCRLF, const char openChar='"', const char closeChar='"' ); /** @p scursor must be positioned right after the opening '(' */ KMIME_EXPORT bool parseComment( const char* &scursor, const char * const send, QString &result, bool isCRLF=false, bool reallySave=true ); /** Parses a phrase. You may or may not have already started parsing into the phrase, but only if it starts with atext. If you setup this function to parse a phrase starting with an encoded-word or quoted-string, @p scursor has to point to the char introducing the encoded-word or quoted-string, resp. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param result the parsed string. @return true if the input phrase was successfully parsed; false otherwise. */ KMIME_EXPORT bool parsePhrase( const char* &scursor, const char * const send, QString &result, bool isCRLF=false ); /** Parses into the initial atom. You may or may not have already started parsing into the initial atom, but not up to it's end. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param result the parsed string. @return true if the input phrase was successfully parsed; false otherwise. */ KMIME_EXPORT bool parseDotAtom( const char* &scursor, const char * const send, QString &result, bool isCRLF=false ); /** Eats comment-folding-white-space, skips whitespace, folding and comments (even nested ones) and stops at the next non-CFWS character. After calling this function, you should check whether @p scursor == @p send (end of header reached). If a comment with unbalanced parantheses is encountered, @p scursor is being positioned on the opening '(' of the outmost comment. @param scursor pointer to the first character beyond the initial '=' of the input string. @param send pointer to end of input buffer. @param isCRLF true if input string is terminated with a CRLF. */ KMIME_EXPORT void eatCFWS( const char* &scursor, const char * const send, bool isCRLF ); KMIME_EXPORT bool parseDomain( const char* &scursor, const char * const send, QString &result, bool isCRLF=false ); KMIME_EXPORT bool parseObsRoute( const char* &scursor, const char * const send, QStringList &result, bool isCRLF=false, bool save=false ); KMIME_EXPORT bool parseAddrSpec( const char* &scursor, const char * const send, Types::AddrSpec &result, bool isCRLF=false ); KMIME_EXPORT bool parseAngleAddr( const char* &scursor, const char * const send, Types::AddrSpec &result, bool isCRLF=false ); /** Parses a single mailbox. RFC 2822, section 3.4 defines a mailbox as follows:
mailbox := addr-spec / ([ display-name ] angle-addr)
KMime also accepts the legacy format of specifying display names:
mailbox := (addr-spec [ "(" display-name ")" ])
   / ([ display-name ] angle-addr)
   / (angle-addr "(" display-name ")")
@param scursor pointer to the first character of the input string @param send pointer to end of input buffer @param result the parsing result @param isCRLF true if input string is terminated with a CRLF. */ KMIME_EXPORT bool parseMailbox( const char* &scursor, const char * const send, Types::Mailbox &result, bool isCRLF=false ); KMIME_EXPORT bool parseGroup( const char* &scursor, const char * const send, Types::Address &result, bool isCRLF=false ); KMIME_EXPORT bool parseAddress( const char* &scursor, const char * const send, Types::Address &result, bool isCRLF=false ); KMIME_EXPORT bool parseAddressList( const char* &scursor, const char * const send, Types::AddressList &result, bool isCRLF=false ); KMIME_EXPORT bool parseParameter( const char* &scursor, const char * const send, QPair &result, bool isCRLF=false ); KMIME_EXPORT bool parseParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF=false ); KMIME_EXPORT bool parseRawParameterList( const char* &scursor, const char * const send, QMap &result, bool isCRLF=false ); /** Parses an integer number. @param scursor pointer to the first character of the input string @param send pointer to end of input buffer @param result the parsing result @returns The number of parsed digits (don't confuse with @p result!) */ KMIME_EXPORT int parseDigits( const char* &scursor, const char* const send, int &result ); KMIME_EXPORT bool parseTime( const char* &scursor, const char * const send, int &hour, int &min, int &sec, long int &secsEastOfGMT, bool &timeZoneKnown, bool isCRLF=false ); KMIME_EXPORT bool parseDateTime( const char* &scursor, const char * const send, KDateTime &result, bool isCRLF=false ); +KMIME_EXPORT KMime::Headers::Base *extractFirstHeader( QByteArray &head ); + +KMIME_EXPORT QList parseHeaders( const QByteArray &head ); + } // namespace HeaderParsing } // namespace KMime #endif // __KMIME_HEADER_PARSING_H__ diff --git a/kmime/kmime_headerfactory.cpp b/kmime/kmime_headerfactory.cpp new file mode 100644 index 000000000..054ef87e5 --- /dev/null +++ b/kmime/kmime_headerfactory.cpp @@ -0,0 +1,118 @@ +/* + kmime_header_factory.cpp + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2009 Constantin Berzan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + @file + This file is part of the API for handling MIME data and + defines the HeaderFactory class. + + @brief + Defines the HeaderFactory class. + + @authors Constantin Berzan \ +*/ + +#include "kmime_headerfactory.h" +#include "kmime_headers.h" + +#include + +#include +#include + +using namespace KMime; + +/** + * @internal + * Private class that helps to provide binary compatibility between releases. + */ +class KMime::HeaderFactoryPrivate +{ + public: + HeaderFactoryPrivate(); + ~HeaderFactoryPrivate(); + + HeaderFactory *const instance; + QHash headers; // Type->obj mapping; with lower-case type. +}; + +K_GLOBAL_STATIC( HeaderFactoryPrivate, sInstance ) + +HeaderFactoryPrivate::HeaderFactoryPrivate() + : instance( new HeaderFactory( this ) ) +{ +} + +HeaderFactoryPrivate::~HeaderFactoryPrivate() +{ + qDeleteAll( headers.values() ); + delete instance; +} + + + +HeaderFactory* HeaderFactory::self() +{ + return sInstance->instance; +} + +Headers::Base *HeaderFactory::createHeader( const QByteArray &type ) +{ + Q_ASSERT( !type.isEmpty() ); + Headers::Base *h = d->headers.value( type.toLower() ); + if( h ) { + return h->clone(); + } else { + kError() << "Unknown header type" << type; + //return new Headers::Generic; + return 0; + } +} + +HeaderFactory::HeaderFactory( HeaderFactoryPrivate *dd ) + : d( dd ) +{ +} + +HeaderFactory::~HeaderFactory() +{ +} + +bool HeaderFactory::registerHeader( Headers::Base *header ) +{ + if( QByteArray( header->type() ).isEmpty() ) { + // This is probably a generic (but not abstract) header, + // like Address or MailboxList. We cannot register those. + kWarning() << "Tried to register header with empty type."; + return false; + } + QByteArray ltype = QByteArray( header->type() ).toLower(); + if( d->headers.contains( ltype ) ) { + kWarning() << "Header of type" << header->type() << "already registered."; + // TODO should we make this an error? + return false; + } + d->headers.insert( ltype, header ); + kDebug() << "registered type" << header->type(); + return true; +} + diff --git a/kmime/kmime_headerfactory.h b/kmime/kmime_headerfactory.h new file mode 100644 index 000000000..315ba69a0 --- /dev/null +++ b/kmime/kmime_headerfactory.h @@ -0,0 +1,77 @@ +/* + kmime_header_factory.h + + KMime, the KDE internet mail/usenet news message library. + Copyright (c) 2009 Constantin Berzan + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +/** + @file + This file is part of the API for handling @ref MIME data and + defines the HeaderFactory class. + + @brief + Defines the HeaderFactory class. + + @authors Constantin Berzan \ +*/ + +#ifndef __KMIME_HEADERFACTORY_H__ +#define __KMIME_HEADERFACTORY_H__ + +#include "kmime_export.h" + +#include + +namespace KMime { + +namespace Headers { + class Base; +} + +class HeaderFactoryPrivate; + +/** + docu TODO +*/ +class KMIME_EXPORT HeaderFactory +{ + public: + static HeaderFactory* self(); + + template inline bool registerHeader() + { + return registerHeader( new T ); + } + + Headers::Base *createHeader( const QByteArray &type ); + + private: + HeaderFactory( HeaderFactoryPrivate *dd ); + HeaderFactory( const HeaderFactory &other ); // undefined + HeaderFactory& operator=( const HeaderFactory &other ); // undefined + ~HeaderFactory(); + + bool registerHeader( Headers::Base *header ); + + friend class HeaderFactoryPrivate; + HeaderFactoryPrivate *const d; +}; + +} // namespace KMime + +#endif /* __KMIME_HEADERFACTORY_H__ */ diff --git a/kmime/kmime_headers.cpp b/kmime/kmime_headers.cpp index d531d3ed0..d215cf854 100644 --- a/kmime/kmime_headers.cpp +++ b/kmime/kmime_headers.cpp @@ -1,2126 +1,2174 @@ /* -*- c++ -*- kmime_headers.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001-2002 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License assert published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for morbe details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the various header classes: - header's base class defining the common interface - generic base classes for different types of fields - incompatible, Structured-based field classes - compatible, Unstructured-based field classes @brief Defines the various headers classes. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #include "kmime_headers.h" #include "kmime_headers_p.h" #include "kmime_util.h" #include "kmime_content.h" #include "kmime_codecs.h" #include "kmime_header_parsing.h" +#include "kmime_headerfactory.h" #include "kmime_warning.h" #include #include #include #include #include #include #include +template +bool registerHeaderHelper() +{ + const T dummy; + if( QByteArray( dummy.type() ).isEmpty() ) { + // This is a generic header. + return false; + } + return KMime::HeaderFactory::self()->registerHeader(); +} + +// macro to register a header with HeaderFactory +#define kmime_register_header( subclass ) \ +namespace { const bool dummyForRegistering##subclass = registerHeaderHelper(); } + // macro to generate a default constructor implementation #define kmime_mk_trivial_ctor( subclass, baseclass ) \ subclass::subclass( Content *parent ) : baseclass( parent ) \ { \ clear(); \ } \ \ subclass::subclass( Content *parent, const QByteArray &s ) : baseclass( parent ) \ { \ from7BitString( s ); \ } \ \ subclass::subclass( Content *parent, const QString &s, const QByteArray &charset ) : \ baseclass( parent ) \ { \ fromUnicodeString( s, charset ); \ } \ \ -subclass::~subclass() {} +Base *subclass::clone() const \ +{ \ + subclass *ret = new subclass; \ + ret->from7BitString( as7BitString( false ) ); \ + return ret; \ +} \ + \ +subclass::~subclass() {} \ + \ +kmime_register_header( subclass ) +// end kmime_mk_trivial_ctor #define kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ subclass::subclass( Content *parent ) : baseclass( new subclass##Private, parent ) \ { \ clear(); \ } \ \ subclass::subclass( Content *parent, const QByteArray &s ) : baseclass( new subclass##Private, parent ) \ { \ from7BitString( s ); \ } \ \ subclass::subclass( Content *parent, const QString &s, const QByteArray &charset ) : \ baseclass( new subclass##Private, parent ) \ { \ fromUnicodeString( s, charset ); \ } \ \ -subclass::~subclass() {} +Base *subclass::clone() const \ +{ \ + subclass *ret = new subclass; \ + ret->from7BitString( as7BitString( false ) ); \ + return ret; \ +} \ + \ +subclass::~subclass() {} \ + \ +kmime_register_header( subclass ) +// end kmime_mk_trivial_ctor_with_dptr + #define kmime_mk_trivial_ctor_with_name( subclass, baseclass, name ) \ kmime_mk_trivial_ctor( subclass, baseclass ) \ \ const char *subclass::type() const \ { \ return #name; \ } #define kmime_mk_trivial_ctor_with_name_and_dptr( subclass, baseclass, name ) \ kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ const char *subclass::type() const { return #name; } #define kmime_mk_dptr_ctor( subclass, baseclass ) \ subclass::subclass( subclass##Private *d, KMime::Content *parent ) : baseclass( d, parent ) {} using namespace KMime; using namespace KMime::Headers; using namespace KMime::Types; using namespace KMime::HeaderParsing; namespace KMime { namespace Headers { //--------------------------------------- Base::Base( KMime::Content *parent ) : d_ptr( new BasePrivate ) { Q_D(Base); d->parent = parent; } Base::Base( BasePrivate *dd, KMime::Content *parent ) : d_ptr( dd ) { Q_D(Base); d->parent = parent; } Base::~Base() { delete d_ptr; + d_ptr = 0; } KMime::Content *Base::parent() const { return d_ptr->parent; } void Base::setParent( KMime::Content *parent ) { d_ptr->parent = parent; } QByteArray Base::rfc2047Charset() const { if ( d_ptr->encCS.isEmpty() || forceDefaultCharset() ) { return defaultCharset(); } else { return d_ptr->encCS; } } void Base::setRFC2047Charset( const QByteArray &cs ) { d_ptr->encCS = cachedCharset( cs ); } bool Base::forceDefaultCharset() const { return ( parent() != 0 ? parent()->forceDefaultCharset() : false ); } QByteArray Base::defaultCharset() const { return ( parent() != 0 ? parent()->defaultCharset() : Latin1 ); } const char *Base::type() const { return ""; } bool Base::is( const char *t ) const { return strcasecmp( t, type() ) == 0; } bool Base::isMimeHeader() const { return strncasecmp( type(), "Content-", 8 ) == 0; } bool Base::isXHeader() const { return strncmp( type(), "X-", 2 ) == 0; } QByteArray Base::typeIntro() const { return QByteArray( type() ) + ": "; } //-------------------------------------- namespace Generics { //------------------------------ //@cond PRIVATE kmime_mk_dptr_ctor( Unstructured, Base ) //@endcond Unstructured::Unstructured( Content *p ) : Base( new UnstructuredPrivate, p ) { } Unstructured::Unstructured( Content *p, const QByteArray &s ) : Base( new UnstructuredPrivate, p ) { from7BitString( s ); } Unstructured::Unstructured( Content *p, const QString &s, const QByteArray &cs ) : Base( new UnstructuredPrivate, p ) { fromUnicodeString( s, cs ); } Unstructured::~Unstructured() { } void Unstructured::from7BitString( const QByteArray &s ) { Q_D(Unstructured); d->decoded = decodeRFC2047String( s, d->encCS, defaultCharset(), forceDefaultCharset() ); } QByteArray Unstructured::as7BitString( bool withHeaderType ) const { const Q_D(Unstructured); QByteArray result; if ( withHeaderType ) { result = typeIntro(); } result += encodeRFC2047String( d->decoded, d->encCS ) ; return result; } void Unstructured::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(Unstructured); d->decoded = s; d->encCS = cachedCharset( b ); } QString Unstructured::asUnicodeString() const { return d_func()->decoded; } void Unstructured::clear() { Q_D(Unstructured); d->decoded.truncate( 0 ); } bool Unstructured::isEmpty() const { return d_func()->decoded.isEmpty(); } //------------------------------ //------------------------------ Structured::Structured( Content *p ) : Base( new StructuredPrivate, p ) { } Structured::Structured( Content *p, const QByteArray &s ) : Base( new StructuredPrivate, p ) { from7BitString( s ); } Structured::Structured( Content *p, const QString &s, const QByteArray &cs ) : Base( new StructuredPrivate, p ) { fromUnicodeString( s, cs ); } kmime_mk_dptr_ctor( Structured, Base ) Structured::~Structured() { } void Structured::from7BitString( const QByteArray &s ) { Q_D(Structured); if ( d->encCS.isEmpty() ) { d->encCS = defaultCharset(); } const char *cursor = s.constData(); parse( cursor, cursor + s.length() ); } QString Structured::asUnicodeString() const { return QString::fromLatin1( as7BitString( false ) ); } void Structured::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(Structured); d->encCS = cachedCharset( b ); from7BitString( s.toLatin1() ); } //------------------------------ //-----
------------------------- Address::Address( Content *p ) : Structured( new AddressPrivate, p ) { } Address::Address( Content *p, const QByteArray &s ) : Structured( new AddressPrivate, p ) { from7BitString( s ); } Address::Address( Content *p, const QString &s, const QByteArray &cs ) : Structured( new AddressPrivate, p ) { fromUnicodeString( s, cs ); } kmime_mk_dptr_ctor( Address, Structured ) Address:: ~Address() { } // helper method used in AddressList and MailboxList static bool stringToMailbox( const QByteArray &address, const QString &displayName, Types::Mailbox &mbox ) { Types::AddrSpec addrSpec; mbox.setName( displayName ); const char *cursor = address.constData(); if ( !parseAngleAddr( cursor, cursor + address.length(), addrSpec ) ) { if ( !parseAddrSpec( cursor, cursor + address.length(), addrSpec ) ) { kWarning() << "Invalid address"; return false; } } mbox.setAddress( addrSpec ); return true; } //-----
------------------------- //------------------------------ kmime_mk_trivial_ctor_with_dptr( MailboxList, Address ) kmime_mk_dptr_ctor( MailboxList, Address ) QByteArray MailboxList::as7BitString( bool withHeaderType ) const { const Q_D(MailboxList); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } foreach ( Types::Mailbox mbox, d->mailboxList ) { rv += mbox.as7BitString( d->encCS ); rv += ", "; } rv.resize( rv.length() - 2 ); return rv; } void MailboxList::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(MailboxList); d->encCS = cachedCharset( b ); from7BitString( encodeRFC2047String( s, b, false ) ); } QString MailboxList::asUnicodeString() const { return prettyAddresses().join( QLatin1String( ", " ) ); } void MailboxList::clear() { Q_D(MailboxList); d->mailboxList.clear(); } bool MailboxList::isEmpty() const { return d_func()->mailboxList.isEmpty(); } void MailboxList::addAddress( const Types::Mailbox &mbox ) { Q_D(MailboxList); d->mailboxList.append( mbox ); } void MailboxList::addAddress( const QByteArray &address, const QString &displayName ) { Q_D(MailboxList); Types::Mailbox mbox; if ( stringToMailbox( address, displayName, mbox ) ) { d->mailboxList.append( mbox ); } } QList< QByteArray > MailboxList::addresses() const { QList rv; foreach ( Types::Mailbox mbox, d_func()->mailboxList ) { rv.append( mbox.address() ); } return rv; } QStringList MailboxList::displayNames() const { QStringList rv; foreach ( Types::Mailbox mbox, d_func()->mailboxList ) { rv.append( mbox.name() ); } return rv; } QStringList MailboxList::prettyAddresses() const { QStringList rv; foreach ( Types::Mailbox mbox, d_func()->mailboxList ) { rv.append( mbox.prettyAddress() ); } return rv; } Types::Mailbox::List MailboxList::mailboxes() const { return d_func()->mailboxList; } bool MailboxList::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(MailboxList); // examples: // from := "From:" mailbox-list CRLF // sender := "Sender:" mailbox CRLF // parse an address-list: QList maybeAddressList; if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) { return false; } d->mailboxList.clear(); // extract the mailboxes and complain if there are groups: QList::Iterator it; for ( it = maybeAddressList.begin(); it != maybeAddressList.end() ; ++it ) { if ( !(*it).displayName.isEmpty() ) { KMIME_WARN << "mailbox groups in header disallowing them! Name: \"" << (*it).displayName << "\"" << endl; } d->mailboxList += (*it).mailboxList; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( SingleMailbox, MailboxList ) //@endcond bool SingleMailbox::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(MailboxList); if ( !MailboxList::parse( scursor, send, isCRLF ) ) { return false; } if ( d->mailboxList.count() > 1 ) { KMIME_WARN << "multiple mailboxes in header allowing only a single one!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( AddressList, Address ) kmime_mk_dptr_ctor( AddressList, Address ) //@endcond QByteArray AddressList::as7BitString( bool withHeaderType ) const { const Q_D(AddressList); if ( d->addressList.isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } foreach ( Types::Address addr, d->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv += mbox.as7BitString( d->encCS ); rv += ", "; } } rv.resize( rv.length() - 2 ); return rv; } void AddressList::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_D(AddressList); d->encCS = cachedCharset( b ); from7BitString( encodeRFC2047String( s, b, false ) ); } QString AddressList::asUnicodeString() const { return prettyAddresses().join( QLatin1String( ", " ) ); } void AddressList::clear() { Q_D(AddressList); d->addressList.clear(); } bool AddressList::isEmpty() const { return d_func()->addressList.isEmpty(); } void AddressList::addAddress( const Types::Mailbox &mbox ) { Q_D(AddressList); Types::Address addr; addr.mailboxList.append( mbox ); d->addressList.append( addr ); } void AddressList::addAddress( const QByteArray &address, const QString &displayName ) { Q_D(AddressList); Types::Address addr; Types::Mailbox mbox; if ( stringToMailbox( address, displayName, mbox ) ) { addr.mailboxList.append( mbox ); d->addressList.append( addr ); } } QList< QByteArray > AddressList::addresses() const { QList rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox.address() ); } } return rv; } QStringList AddressList::displayNames() const { QStringList rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox.name() ); } } return rv; } QStringList AddressList::prettyAddresses() const { QStringList rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox.prettyAddress() ); } } return rv; } Types::Mailbox::List AddressList::mailboxes() const { Types::Mailbox::List rv; foreach ( Types::Address addr, d_func()->addressList ) { foreach ( Types::Mailbox mbox, addr.mailboxList ) { rv.append( mbox ); } } return rv; } bool AddressList::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(AddressList); QList maybeAddressList; if ( !parseAddressList( scursor, send, maybeAddressList, isCRLF ) ) { return false; } d->addressList = maybeAddressList; return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( Token, Structured ) kmime_mk_dptr_ctor( Token, Structured ) //@endcond QByteArray Token::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } if ( withHeaderType ) { return typeIntro() + d_func()->token; } return d_func()->token; } void Token::clear() { Q_D(Token); d->token.clear(); } bool Token::isEmpty() const { return d_func()->token.isEmpty(); } QByteArray Token::token() const { return d_func()->token; } void Token::setToken( const QByteArray &t ) { Q_D(Token); d->token = t; } bool Token::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Token); clear(); eatCFWS( scursor, send, isCRLF ); // must not be empty: if ( scursor == send ) { return false; } QPair maybeToken; if ( !parseToken( scursor, send, maybeToken, false /* no 8bit chars */ ) ) { return false; } d->token = QByteArray( maybeToken.first, maybeToken.second ); // complain if trailing garbage is found: eatCFWS( scursor, send, isCRLF ); if ( scursor != send ) { KMIME_WARN << "trailing garbage after token in header allowing " "only a single token!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( PhraseList, Structured ) //@endcond QByteArray PhraseList::as7BitString( bool withHeaderType ) const { const Q_D(PhraseList); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } for ( int i = 0; i < d->phraseList.count(); ++i ) { // FIXME: only encode when needed, quote when needed, etc. rv += encodeRFC2047String( d->phraseList[i], d->encCS, false, false ); if ( i != d->phraseList.count() - 1 ) { rv += ", "; } } return rv; } QString PhraseList::asUnicodeString() const { return d_func()->phraseList.join( QLatin1String( ", " ) ); } void PhraseList::clear() { Q_D(PhraseList); d->phraseList.clear(); } bool PhraseList::isEmpty() const { return d_func()->phraseList.isEmpty(); } QStringList PhraseList::phrases() const { return d_func()->phraseList; } bool PhraseList::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(PhraseList); d->phraseList.clear(); while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // empty entry ending the list: OK. if ( scursor == send ) { return true; } // empty entry: ignore. if ( *scursor == ',' ) { scursor++; continue; } QString maybePhrase; if ( !parsePhrase( scursor, send, maybePhrase, isCRLF ) ) { return false; } d->phraseList.append( maybePhrase ); eatCFWS( scursor, send, isCRLF ); // non-empty entry ending the list: OK. if ( scursor == send ) { return true; } // comma separating the phrases: eat. if ( *scursor == ',' ) { scursor++; } } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( DotAtom, Structured ) //@endcond QByteArray DotAtom::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += d_func()->dotAtom.toLatin1(); // FIXME: encoding? return rv; } QString DotAtom::asUnicodeString() const { return d_func()->dotAtom; } void DotAtom::clear() { Q_D(DotAtom); d->dotAtom.clear(); } bool DotAtom::isEmpty() const { return d_func()->dotAtom.isEmpty(); } bool DotAtom::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(DotAtom); QString maybeDotAtom; if ( !parseDotAtom( scursor, send, maybeDotAtom, isCRLF ) ) { return false; } d->dotAtom = maybeDotAtom; eatCFWS( scursor, send, isCRLF ); if ( scursor != send ) { KMIME_WARN << "trailing garbage after dot-atom in header allowing " "only a single dot-atom!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( Parametrized, Structured ) kmime_mk_dptr_ctor( Parametrized, Structured ) //@endcond QByteArray Parametrized::as7BitString( bool withHeaderType ) const { const Q_D(Parametrized); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } bool first = true; for ( QMap::ConstIterator it = d->parameterHash.constBegin(); it != d->parameterHash.constEnd(); ++it ) { if ( !first ) { rv += "; "; } else { first = false; } rv += it.key().toLatin1() + '='; if ( isUsAscii( it.value() ) ) { QByteArray tmp = it.value().toLatin1(); addQuotes( tmp, true ); // force quoting, eg. for whitespaces in parameter value rv += tmp; } else { // FIXME: encoded strings are not allowed inside quotes, OTOH we need to quote whitespaces... rv += "\"" + encodeRFC2047String( it.value(), d->encCS ) + "\""; } } return rv; } QString Parametrized::parameter( const QString &key ) const { return d_func()->parameterHash.value( key ); } void Parametrized::setParameter( const QString &key, const QString &value ) { Q_D(Parametrized); d->parameterHash.insert( key, value ); } bool Parametrized::isEmpty() const { return d_func()->parameterHash.isEmpty(); } void Parametrized::clear() { Q_D(Parametrized); d->parameterHash.clear(); } bool Parametrized::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(Parametrized); d->parameterHash.clear(); if ( !parseParameterList( scursor, send, d->parameterHash, isCRLF ) ) { return false; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( Ident, Address ) kmime_mk_dptr_ctor( Ident, Address ) //@endcond QByteArray Ident::as7BitString( bool withHeaderType ) const { const Q_D(Ident); if ( d->msgIdList.isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv = typeIntro(); } foreach ( Types::AddrSpec addr, d->msgIdList ) { rv += '<'; rv += addr.asString().toLatin1(); // FIXME: change parsing to use QByteArrays rv += "> "; } rv.resize( rv.length() - 1 ); return rv; } void Ident::clear() { Q_D(Ident); d->msgIdList.clear(); } bool Ident::isEmpty() const { return d_func()->msgIdList.isEmpty(); } bool Ident::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(Ident); // msg-id := "<" id-left "@" id-right ">" // id-left := dot-atom-text / no-fold-quote / local-part // id-right := dot-atom-text / no-fold-literal / domain // // equivalent to: // msg-id := angle-addr d->msgIdList.clear(); while ( scursor != send ) { eatCFWS( scursor, send, isCRLF ); // empty entry ending the list: OK. if ( scursor == send ) { return true; } // empty entry: ignore. if ( *scursor == ',' ) { scursor++; continue; } AddrSpec maybeMsgId; if ( !parseAngleAddr( scursor, send, maybeMsgId, isCRLF ) ) { return false; } d->msgIdList.append( maybeMsgId ); eatCFWS( scursor, send, isCRLF ); // header end ending the list: OK. if ( scursor == send ) { return true; } // regular item separator: eat it. if ( *scursor == ',' ) { scursor++; } } return true; } QList Ident::identifiers() const { QList rv; foreach ( Types::AddrSpec addr, d_func()->msgIdList ) { rv.append( addr.asString().toLatin1() ); // FIXME change parsing to create QByteArrays } return rv; } void Ident::appendIdentifier( const QByteArray &id ) { Q_D(Ident); QByteArray tmp = id; if ( !tmp.startsWith( '<' ) ) { tmp.prepend( '<' ); } if ( !tmp.endsWith( '>' ) ) { tmp.append( '>' ); } AddrSpec msgId; const char *cursor = tmp.constData(); if ( parseAngleAddr( cursor, cursor + tmp.length(), msgId ) ) { d->msgIdList.append( msgId ); } else { kWarning() << "Unable to parse address spec!"; } } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr( SingleIdent, Ident ) //@endcond QByteArray SingleIdent::identifier() const { if ( d_func()->msgIdList.isEmpty() ) { return QByteArray(); } return identifiers().first(); } void SingleIdent::setIdentifier( const QByteArray &id ) { Q_D(SingleIdent); d->msgIdList.clear(); appendIdentifier( id ); } bool SingleIdent::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(SingleIdent); if ( !Ident::parse( scursor, send, isCRLF ) ) { return false; } if ( d->msgIdList.count() > 1 ) { KMIME_WARN << "more than one msg-id in header " << "allowing only a single one!" << endl; } return true; } //------------------------------ } // namespace Generics //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ReturnPath, Generics::Address, Return-Path ) //@endcond QByteArray ReturnPath::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += '<' + d_func()->mailbox.as7BitString( d_func()->encCS ) + '>'; return rv; } void ReturnPath::clear() { Q_D(ReturnPath); d->mailbox.setAddress( Types::AddrSpec() ); d->mailbox.setName( QString() ); } bool ReturnPath::isEmpty() const { const Q_D(ReturnPath); return !d->mailbox.hasAddress() && !d->mailbox.hasName(); } bool ReturnPath::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(ReturnPath); eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } const char * oldscursor = scursor; Mailbox maybeMailbox; if ( !parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { // mailbox parsing failed, but check for empty brackets: scursor = oldscursor; if ( *scursor != '<' ) { return false; } scursor++; eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '>' ) { return false; } scursor++; // prepare a Null mailbox: AddrSpec emptyAddrSpec; maybeMailbox.setName( QString() ); maybeMailbox.setAddress( emptyAddrSpec ); } else { // check that there was no display-name: if ( maybeMailbox.hasName() ) { KMIME_WARN << "display-name \"" << maybeMailbox.name() << "\" in Return-Path!" << endl; } } d->mailbox = maybeMailbox; // see if that was all: eatCFWS( scursor, send, isCRLF ); // and warn if it wasn't: if ( scursor != send ) { KMIME_WARN << "trailing garbage after angle-addr in Return-Path!" << endl; } return true; } //------------------------------ //------------------------------------ +// NOTE: Do *not* register Generic with HeaderFactory, since its type() is changeable. + Generic::Generic() : Generics::Unstructured( new GenericPrivate ) { } Generic::Generic( const char *t ) : Generics::Unstructured( new GenericPrivate ) { setType( t ); } Generic::Generic( const char *t, Content *p ) : Generics::Unstructured( new GenericPrivate, p ) { setType( t ); } Generic::Generic( const char *t, Content *p, const QByteArray &s ) : Generics::Unstructured( new GenericPrivate, p ) { from7BitString( s ); setType( t ); } Generic::Generic( const char *t, Content *p, const QString &s, const QByteArray &cs ) : Generics::Unstructured( new GenericPrivate, p ) { fromUnicodeString( s, cs ); setType( t ); } Generic::~Generic() { } void Generic::clear() { Q_D(Generic); delete[] d->type; d->type = 0; Unstructured::clear(); } +Base *Generic::clone() const +{ + Q_D( const Generic ); + Generic *ret = new Generic( d->type ); + ret->from7BitString( as7BitString( false ) ); + return ret; +} + bool Generic::isEmpty() const { return d_func()->type == 0 || Unstructured::isEmpty(); } const char *Generic::type() const { return d_func()->type; } void Generic::setType( const char *type ) { Q_D(Generic); if ( d->type ) { delete[] d->type; } if ( type ) { d->type = new char[strlen( type )+1]; strcpy( d->type, type ); } else { d->type = 0; } } //------------------------------------ //---------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name( MessageID, Generics::SingleIdent, Message-Id ) //@endcond void MessageID::generate( const QByteArray &fqdn ) { setIdentifier( uniqueString() + '@' + fqdn + '>' ); } //--------------------------------- //------------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Control, Generics::Structured, Control ) //@endcond QByteArray Control::as7BitString( bool withHeaderType ) const { const Q_D(Control); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += d->name; if ( !d->parameter.isEmpty() ) { rv += ' ' + d->parameter; } return rv; } void Control::clear() { Q_D(Control); d->name.clear(); d->parameter.clear(); } bool Control::isEmpty() const { return d_func()->name.isEmpty(); } QByteArray Control::controlType() const { return d_func()->name; } QByteArray Control::parameter() const { return d_func()->parameter; } bool Control::isCancel() const { return d_func()->name.toLower() == "cancel"; } void Control::setCancel( const QByteArray &msgid ) { Q_D(Control); d->name = "cancel"; d->parameter = msgid; } bool Control::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Control); clear(); eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } const char *start = scursor; while ( scursor != send && !isspace( *scursor ) ) { ++scursor; } d->name = QByteArray( start, scursor - start ); eatCFWS( scursor, send, isCRLF ); d->parameter = QByteArray( scursor, send - scursor ); return true; } //----------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( MailCopiesTo, Generics::AddressList, Mail-Copies-To ) //@endcond QByteArray MailCopiesTo::as7BitString( bool withHeaderType ) const { QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } if ( !AddressList::isEmpty() ) { rv += AddressList::as7BitString( false ); } else { if ( d_func()->alwaysCopy ) { rv += "poster"; } else if ( d_func()->neverCopy ) { rv += "nobody"; } } return rv; } QString MailCopiesTo::asUnicodeString() const { if ( !AddressList::isEmpty() ) { return AddressList::asUnicodeString(); } if ( d_func()->alwaysCopy ) { return QLatin1String( "poster" ); } if ( d_func()->neverCopy ) { return QLatin1String( "nobody" ); } return QString(); } void MailCopiesTo::clear() { Q_D(MailCopiesTo); AddressList::clear(); d->alwaysCopy = false; d->neverCopy = false; } bool MailCopiesTo::isEmpty() const { return AddressList::isEmpty() && !(d_func()->alwaysCopy || d_func()->neverCopy); } bool MailCopiesTo::alwaysCopy() const { return !AddressList::isEmpty() || d_func()->alwaysCopy; } void MailCopiesTo::setAlwaysCopy() { Q_D(MailCopiesTo); clear(); d->alwaysCopy = true; } bool MailCopiesTo::neverCopy() const { return d_func()->neverCopy; } void MailCopiesTo::setNeverCopy() { Q_D(MailCopiesTo); clear(); d->neverCopy = true; } bool MailCopiesTo::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(MailCopiesTo); clear(); if ( send - scursor == 5 ) { if ( qstrnicmp( "never", scursor, 5 ) == 0 ) { d->neverCopy = true; return true; } } if ( send - scursor == 6 ) { if ( qstrnicmp( "always", scursor, 6 ) == 0 || qstrnicmp( "poster", scursor, 6 ) == 0 ) { d->alwaysCopy = true; return true; } if ( qstrnicmp( "nobody", scursor, 6 ) == 0 ) { d->alwaysCopy = true; return true; } } return AddressList::parse( scursor, send, isCRLF ); } //------------------------------ //--------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Date, Generics::Structured, Date ) //@endcond QByteArray Date::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += d_func()->dateTime.toString( KDateTime::RFCDateDay ).toLatin1(); return rv; } void Date::clear() { Q_D(Date); d->dateTime = KDateTime(); } bool Date::isEmpty() const { return d_func()->dateTime.isNull() || !d_func()->dateTime.isValid(); } KDateTime Date::dateTime() const { return d_func()->dateTime; } void Date::setDateTime( const KDateTime &dt ) { Q_D(Date); d->dateTime = dt; } int Date::ageInDays() const { QDate today = QDate::currentDate(); return dateTime().date().daysTo(today); } bool Date::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Date); return parseDateTime( scursor, send, d->dateTime, isCRLF ); } //-------------------------------------- //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Newsgroups, Generics::Structured, Newsgroups ) kmime_mk_trivial_ctor_with_name( FollowUpTo, Newsgroups, Followup-To ) //@endcond QByteArray Newsgroups::as7BitString( bool withHeaderType ) const { const Q_D(Newsgroups); if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } for ( int i = 0; i < d->groups.count(); ++i ) { rv += d->groups[ i ]; if ( i != d->groups.count() - 1 ) { rv += ','; } } return rv; } void Newsgroups::fromUnicodeString( const QString &s, const QByteArray &b ) { Q_UNUSED( b ); Q_D(Newsgroups); from7BitString( s.toUtf8() ); d->encCS = cachedCharset( "UTF-8" ); } QString Newsgroups::asUnicodeString() const { return QString::fromUtf8( as7BitString( false ) ); } void Newsgroups::clear() { Q_D(Newsgroups); d->groups.clear(); } bool Newsgroups::isEmpty() const { return d_func()->groups.isEmpty(); } QList Newsgroups::groups() const { return d_func()->groups; } void Newsgroups::setGroups( const QList &groups ) { Q_D(Newsgroups); d->groups = groups; } bool Newsgroups::isCrossposted() const { return d_func()->groups.count() >= 2; } bool Newsgroups::parse( const char* &scursor, const char *const send, bool isCRLF ) { Q_D(Newsgroups); clear(); forever { eatCFWS( scursor, send, isCRLF ); if ( scursor != send && *scursor == ',' ) { ++scursor; } eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return true; } const char *start = scursor; while ( scursor != send && !isspace( *scursor ) && *scursor != ',' ) { ++scursor; } QByteArray group( start, scursor - start ); d->groups.append( group ); } return true; } //-------------------------------- //-------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( Lines, Generics::Structured, Lines ) //@endcond QByteArray Lines::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray num; num.setNum( d_func()->lines ); if ( withHeaderType ) { return typeIntro() + num; } return num; } QString Lines::asUnicodeString() const { if ( isEmpty() ) { return QString(); } return QString::number( d_func()->lines ); } void Lines::clear() { Q_D(Lines); d->lines = -1; } bool Lines::isEmpty() const { return d_func()->lines == -1; } int Lines::numberOfLines() const { return d_func()->lines; } void Lines::setNumberOfLines( int lines ) { Q_D(Lines); d->lines = lines; } bool Lines::parse( const char* &scursor, const char* const send, bool isCRLF ) { Q_D(Lines); eatCFWS( scursor, send, isCRLF ); if ( parseDigits( scursor, send, d->lines ) == 0 ) { clear(); return false; } return true; } //------------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ContentType, Generics::Parametrized, Content-Type ) //@endcond bool ContentType::isEmpty() const { return d_func()->mimeType.isEmpty(); } void ContentType::clear() { Q_D(ContentType); d->category = CCsingle; d->mimeType.clear(); d->mimeSubType.clear(); Parametrized::clear(); } QByteArray ContentType::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } rv += mimeType(); if ( !Parametrized::isEmpty() ) { rv += "; " + Parametrized::as7BitString( false ); } return rv; } QByteArray ContentType::mimeType() const { return d_func()->mimeType + '/' + d_func()->mimeSubType; } QByteArray ContentType::mediaType() const { return d_func()->mimeType; } QByteArray ContentType::subType() const { return d_func()->mimeSubType; } void ContentType::setMimeType( const QByteArray &mimeType ) { Q_D(ContentType); int pos = mimeType.indexOf( '/' ); if ( pos < 0 ) { d->mimeType = mimeType; d->mimeSubType.clear(); } else { d->mimeType = mimeType.left( pos ); d->mimeSubType = mimeType.mid( pos + 1 ); } Parametrized::clear(); if ( isMultipart() ) { d->category = CCcontainer; } else { d->category = CCsingle; } } bool ContentType::isMediatype( const char *mediatype ) const { return strncasecmp( mediaType().constData(), mediatype, strlen( mediatype ) ) == 0; } bool ContentType::isSubtype( const char *subtype ) const { return strncasecmp( subType().constData(), subtype, strlen( subtype ) ) == 0; } bool ContentType::isText() const { return ( strncasecmp( mediaType().constData(), "text", 4 ) == 0 || isEmpty() ); } bool ContentType::isPlainText() const { return ( strcasecmp( mimeType().constData(), "text/plain" ) == 0 || isEmpty() ); } bool ContentType::isHTMLText() const { return strcasecmp( mimeType().constData(), "text/html" ) == 0; } bool ContentType::isImage() const { return strncasecmp( mediaType().constData(), "image", 5 ) == 0; } bool ContentType::isMultipart() const { return strncasecmp( mediaType().constData(), "multipart", 9 ) == 0; } bool ContentType::isPartial() const { return strcasecmp( mimeType().constData(), "message/partial" ) == 0; } QByteArray ContentType::charset() const { QByteArray ret = parameter( "charset" ).toLatin1(); if ( ret.isEmpty() || forceDefaultCharset() ) { //return the default-charset if necessary ret = defaultCharset(); } return ret; } void ContentType::setCharset( const QByteArray &s ) { setParameter( "charset", QString::fromLatin1( s ) ); } QByteArray ContentType::boundary() const { return parameter( "boundary" ).toLatin1(); } void ContentType::setBoundary( const QByteArray &s ) { setParameter( "boundary", QString::fromLatin1( s ) ); } QString ContentType::name() const { return parameter( "name" ); } void ContentType::setName( const QString &s, const QByteArray &cs ) { Q_D(ContentType); d->encCS = cs; setParameter( "name", s ); } QByteArray ContentType::id() const { return parameter( "id" ).toLatin1(); } void ContentType::setId( const QByteArray &s ) { setParameter( "id", s ); } int ContentType::partialNumber() const { QByteArray p = parameter( "number" ).toLatin1(); if ( !p.isEmpty() ) { return p.toInt(); } else { return -1; } } int ContentType::partialCount() const { QByteArray p = parameter( "total" ).toLatin1(); if ( !p.isEmpty() ) { return p.toInt(); } else { return -1; } } contentCategory ContentType::category() const { return d_func()->category; } void ContentType::setCategory( contentCategory c ) { Q_D(ContentType); d->category = c; } void ContentType::setPartialParams( int total, int number ) { setParameter( "number", QString::number( number ) ); setParameter( "total", QString::number( total ) ); } bool ContentType::parse( const char* &scursor, const char * const send, bool isCRLF ) { Q_D(ContentType); // content-type: type "/" subtype *(";" parameter) clear(); eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; // empty header } // type QPair maybeMimeType; if ( !parseToken( scursor, send, maybeMimeType, false /* no 8Bit */ ) ) { return false; } d->mimeType = QByteArray( maybeMimeType.first, maybeMimeType.second ).toLower(); // subtype eatCFWS( scursor, send, isCRLF ); if ( scursor == send || *scursor != '/' ) { return false; } scursor++; eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } QPair maybeSubType; if ( !parseToken( scursor, send, maybeSubType, false /* no 8bit */ ) ) { return false; } d->mimeSubType = QByteArray( maybeSubType.first, maybeSubType.second ).toLower(); // parameter list eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { goto success; // no parameters } if ( *scursor != ';' ) { return false; } scursor++; if ( !Parametrized::parse( scursor, send, isCRLF ) ) { return false; } // adjust category success: if ( isMultipart() ) { d->category = CCcontainer; } else { d->category = CCsingle; } return true; } //------------------------------ //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ContentTransferEncoding, Generics::Token, Content-Transfer-Encoding ) //@endcond typedef struct { const char *s; int e; } encTableType; static const encTableType encTable[] = { { "7Bit", CE7Bit }, { "8Bit", CE8Bit }, { "quoted-printable", CEquPr }, { "base64", CEbase64 }, { "x-uuencode", CEuuenc }, { "binary", CEbinary }, { 0, 0} }; void ContentTransferEncoding::clear() { Q_D(ContentTransferEncoding); d->decoded = true; d->cte = CE7Bit; Token::clear(); } contentEncoding ContentTransferEncoding::encoding() const { return d_func()->cte; } void ContentTransferEncoding::setEncoding( contentEncoding e ) { Q_D(ContentTransferEncoding); d->cte = e; for ( int i = 0; encTable[i].s != 0; ++i ) { if ( d->cte == encTable[i].e ) { setToken( encTable[i].s ); break; } } } bool ContentTransferEncoding::decoded() const { return d_func()->decoded; } void ContentTransferEncoding::setDecoded( bool decoded ) { Q_D(ContentTransferEncoding); d->decoded = decoded; } bool ContentTransferEncoding::needToEncode() const { const Q_D(ContentTransferEncoding); return d->decoded && (d->cte == CEquPr || d->cte == CEbase64); } bool ContentTransferEncoding::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(ContentTransferEncoding); clear(); if ( !Token::parse( scursor, send, isCRLF ) ) { return false; } // TODO: error handling in case of an unknown encoding? for ( int i = 0; encTable[i].s != 0; ++i ) { if ( strcasecmp( token().constData(), encTable[i].s ) == 0 ) { d->cte = ( contentEncoding )encTable[i].e; break; } } d->decoded = ( d->cte == CE7Bit || d->cte == CE8Bit ); return true; } //-------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr( ContentDisposition, Generics::Parametrized, Content-Disposition ) //@endcond QByteArray ContentDisposition::as7BitString( bool withHeaderType ) const { if ( isEmpty() ) { return QByteArray(); } QByteArray rv; if ( withHeaderType ) { rv += typeIntro(); } if ( d_func()->disposition == CDattachment ) { rv += "attachment"; } else if ( d_func()->disposition == CDinline ) { rv += "inline"; } else { return QByteArray(); } if ( !Parametrized::isEmpty() ) { rv += "; " + Parametrized::as7BitString( false ); } return rv; } bool ContentDisposition::isEmpty() const { return d_func()->disposition == CDInvalid; } void ContentDisposition::clear() { Q_D(ContentDisposition); d->disposition = CDInvalid; Parametrized::clear(); } contentDisposition ContentDisposition::disposition() const { return d_func()->disposition; } void ContentDisposition::setDisposition( contentDisposition disp ) { Q_D(ContentDisposition); d->disposition = disp; } QString KMime::Headers::ContentDisposition::filename() const { return parameter( "filename" ); } void ContentDisposition::setFilename( const QString &filename ) { setParameter( "filename", filename ); } bool ContentDisposition::parse( const char *& scursor, const char * const send, bool isCRLF ) { Q_D(ContentDisposition); clear(); // token QByteArray token; eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return false; } QPair maybeToken; if ( !parseToken( scursor, send, maybeToken, false /* no 8Bit */ ) ) { return false; } token = QByteArray( maybeToken.first, maybeToken.second ).toLower(); if ( token == "inline" ) { d->disposition = CDinline; } else if ( token == "attachment" ) { d->disposition = CDattachment; } else { return false; } // parameter list eatCFWS( scursor, send, isCRLF ); if ( scursor == send ) { return true; // no parameters } if ( *scursor != ';' ) { return false; } scursor++; return Parametrized::parse( scursor, send, isCRLF ); } //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Subject, Generics::Unstructured, Subject ) //@endcond bool Subject::isReply() const { return asUnicodeString().indexOf( QLatin1String( "Re:" ), 0, Qt::CaseInsensitive ) == 0; } //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentDescription, Generics::Unstructured, Content-Description ) kmime_mk_trivial_ctor_with_name( ContentLocation, Generics::Unstructured, Content-Location ) kmime_mk_trivial_ctor_with_name( From, Generics::MailboxList, From ) kmime_mk_trivial_ctor_with_name( Sender, Generics::SingleMailbox, Sender ) kmime_mk_trivial_ctor_with_name( To, Generics::AddressList, To ) kmime_mk_trivial_ctor_with_name( Cc, Generics::AddressList, Cc ) kmime_mk_trivial_ctor_with_name( Bcc, Generics::AddressList, Bcc ) kmime_mk_trivial_ctor_with_name( ReplyTo, Generics::AddressList, Reply-To ) kmime_mk_trivial_ctor_with_name( Keywords, Generics::PhraseList, Keywords ) kmime_mk_trivial_ctor_with_name( MIMEVersion, Generics::DotAtom, MIME-Version ) kmime_mk_trivial_ctor_with_name( ContentID, Generics::SingleIdent, Content-ID ) kmime_mk_trivial_ctor_with_name( Supersedes, Generics::SingleIdent, Supersedes ) kmime_mk_trivial_ctor_with_name( InReplyTo, Generics::Ident, In-Reply-To ) kmime_mk_trivial_ctor_with_name( References, Generics::Ident, References ) kmime_mk_trivial_ctor_with_name( Organization, Generics::Unstructured, Organization ) kmime_mk_trivial_ctor_with_name( UserAgent, Generics::Unstructured, User-Agent ) //@endcond } // namespace Headers } // namespace KMime diff --git a/kmime/kmime_headers.h b/kmime/kmime_headers.h index eff454e5c..03a0427af 100644 --- a/kmime/kmime_headers.h +++ b/kmime/kmime_headers.h @@ -1,1466 +1,1488 @@ /* -*- c++ -*- kmime_headers.h KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001-2002 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the various header classes: - header's base class defining the common interface - generic base classes for different types of fields - incompatible, Structured-based field classes - compatible, Unstructured-based field classes @brief Defines the various headers classes. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #ifndef __KMIME_HEADERS_H__ #define __KMIME_HEADERS_H__ #include "kmime_export.h" #include "kmime_header_parsing.h" #include #include #include #include #include #include #include #include namespace KMime { class Content; namespace Headers { class BasePrivate; enum contentCategory { CCsingle, CCcontainer, CCmixedPart, CCalternativePart }; /** Various possible values for the "Content-Transfer-Encoding" header. */ enum contentEncoding { CE7Bit, ///< 7bit CE8Bit, ///< 8bit CEquPr, ///< quoted-printable CEbase64, ///< base64 CEuuenc, ///< uuencode CEbinary ///< binary }; /** Various possible values for the "Content-Disposition" header. */ enum contentDisposition { CDInvalid, ///< Default, invalid value CDinline, ///< inline CDattachment, ///< attachment CDparallel ///< parallel (invalid, do not use) }; //often used charset // TODO: get rid of this! static const QByteArray Latin1( "ISO-8859-1" ); //@cond PRIVATE // internal macro to generate default constructors #define kmime_mk_trivial_ctor( subclass ) \ public: \ explicit subclass( Content *parent = 0 ); \ subclass( Content *parent, const QByteArray &s ); \ subclass( Content *parent, const QString &s, const QByteArray &charset ); \ - ~subclass(); + ~subclass(); \ + virtual Base *clone() const; #define kmime_mk_dptr_ctor( subclass ) \ protected: \ explicit subclass( subclass##Private *d, KMime::Content *parent = 0 ); #define kmime_mk_trivial_ctor_with_name( subclass ) \ kmime_mk_trivial_ctor( subclass ) \ const char *type() const; //@endcond // // // HEADER'S BASE CLASS. DEFINES THE COMMON INTERFACE // // /** Baseclass of all header-classes. It represents a header-field as described in RFC-822. */ class KMIME_EXPORT Base { public: /** A list of headers. */ typedef QList List; /** Creates an empty header with a parent-content. */ explicit Base( KMime::Content *parent = 0 ); /** Destructor. */ virtual ~Base(); /** Returns the parent of this header. */ KMime::Content *parent() const; /** Sets the parent for this header to @p parent. */ void setParent( KMime::Content *parent ); /** Parses the given string. Take care of RFC2047-encoded strings. @param s The encoded header data. */ virtual void from7BitString( const QByteArray &s ) = 0; /** Returns the encoded header. @param withHeaderType Specifies whether the header-type should be included. */ virtual QByteArray as7BitString( bool withHeaderType = true ) const = 0; /** Returns the charset that is used for RFC2047-encoding. */ QByteArray rfc2047Charset() const; /** Sets the charset for RFC2047-encoding. @param cs The new charset used for RFC2047 encoding. */ void setRFC2047Charset( const QByteArray &cs ); /** Returns the default charset. */ QByteArray defaultCharset() const; /** Returns if the default charset is mandatory. */ bool forceDefaultCharset() const; /** Parses the given string and set the charset. @param s The header data as unicode string. @param b The charset preferred for encoding. */ virtual void fromUnicodeString( const QString &s, const QByteArray &b ) = 0; /** Returns the decoded content of the header without the header-type. */ virtual QString asUnicodeString() const = 0; /** Deletes. */ virtual void clear() = 0; + /** + Makes a copy of this header. + This function does not set the parent of the header it returns. + */ + virtual Base *clone() const = 0; + /** Checks if this header contains any data. */ virtual bool isEmpty() const = 0; /** Returns the type of this header (e.g. "From"). */ virtual const char *type() const; /** Checks if this header is of type @p t. */ bool is( const char *t ) const; /** Checks if this header is a MIME header. */ bool isMimeHeader() const; /** Checks if this header is a X-Header. */ bool isXHeader() const; protected: /** Helper method, returns the header prefix including ":". */ QByteArray typeIntro() const; //@cond PRIVATE BasePrivate *d_ptr; kmime_mk_dptr_ctor( Base ) //@endcond private: Q_DECLARE_PRIVATE(Base) Q_DISABLE_COPY(Base) }; // // // GENERIC BASE CLASSES FOR DIFFERENT TYPES OF FIELDS // // namespace Generics { class UnstructuredPrivate; /** Abstract base class for unstructured header fields (e.g. "Subject", "Comment", "Content-description"). Features: Decodes the header according to RFC2047, incl. RFC2231 extensions to encoded-words. Subclasses need only re-implement @p const @p char* @p type(). */ // known issues: // - uses old decodeRFC2047String function, instead of our own... class KMIME_EXPORT Unstructured : public Base { //@cond PRIVATE kmime_mk_dptr_ctor( Unstructured ) //@endcond public: explicit Unstructured( Content *p = 0 ); Unstructured( Content *p, const QByteArray &s ); Unstructured( Content *p, const QString &s, const QByteArray &cs ); ~Unstructured(); virtual void from7BitString( const QByteArray &s ); virtual QByteArray as7BitString( bool withHeaderType=true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; private: Q_DECLARE_PRIVATE(Unstructured) }; class StructuredPrivate; /** @brief Base class for structured header fields. This is the base class for all structured header fields. It contains parsing methods for all basic token types found in rfc2822. @section Parsing At the basic level, there are tokens & tspecials (rfc2045), atoms & specials, quoted-strings, domain-literals (all rfc822) and encoded-words (rfc2047). As a special token, we have the comment. It is one of the basic tokens defined in rfc822, but it's parsing relies in part on the basic token parsers (e.g. comments may contain encoded-words). Also, most upper-level parsers (notably those for phrase and dot-atom) choose to ignore any comment when parsing. Then there are the real composite tokens, which are made up of one or more of the basic tokens (and semantically invisible comments): phrases (rfc822 with rfc2047) and dot-atoms (rfc2822). This finishes the list of supported token types. Subclasses will provide support for more higher-level tokens, where necessary, using these parsers. @author Marc Mutz */ class KMIME_EXPORT Structured : public Base { public: explicit Structured( Content *p = 0 ); Structured( Content *p, const QByteArray &s ); Structured( Content *p, const QString &s, const QByteArray &cs ); ~Structured(); virtual void from7BitString( const QByteArray &s ); virtual QString asUnicodeString() const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); protected: /** This method parses the raw header and needs to be implemented in every sub-class. @param scursor Pointer to the start of the data still to parse. @param send Pointer to the end of the data. @param isCRLF true if input string is terminated with a CRLF. */ virtual bool parse( const char* &scursor, const char *const send, bool isCRLF = false ) = 0; //@cond PRIVATE kmime_mk_dptr_ctor( Structured ) //@endcond private: Q_DECLARE_PRIVATE(Structured) }; class AddressPrivate; /** Base class for all address related headers. */ class KMIME_EXPORT Address : public Structured { public: explicit Address( Content *p = 0 ); Address( Content *p, const QByteArray &s ); Address( Content *p, const QString &s, const QByteArray &cs ); ~Address(); protected: //@cond PRIVATE kmime_mk_dptr_ctor( Address ) //@endcond private: Q_DECLARE_PRIVATE(Address) }; class MailboxListPrivate; /** Base class for headers that deal with (possibly multiple) addresses, but don't allow groups. @see RFC 2822, section 3.4 */ class KMIME_EXPORT MailboxList : public Address { //@cond PRIVATE kmime_mk_trivial_ctor( MailboxList ) kmime_mk_dptr_ctor( MailboxList ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Adds an address to this header. @param mbox A Mailbox object specifying the address. */ void addAddress( const Types::Mailbox &mbox ); /** Adds an address to this header. @param address The actual email address, with or without angle brackets. @param displayName An optional name associated with the address. */ void addAddress( const QByteArray &address, const QString &displayName = QString() ); /** Returns a list of all addresses in this header, regardless of groups. */ QList addresses() const; /** Returns a list of all display names associated with the addresses in this header. An empty entry is added for addresses that do not have a display name. */ QStringList displayNames() const; /** Returns a list of assembled display name / address strings of the following form: "Display Name <address>". These are unicode strings without any transport encoding, ie. they are only suitable for displaying. */ QStringList prettyAddresses() const; /** Returns a list of mailboxes listed in this header. */ Types::Mailbox::List mailboxes() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(MailboxList) }; class SingleMailboxPrivate; /** Base class for headers that deal with exactly one mailbox (e.g. Sender). */ class KMIME_EXPORT SingleMailbox : public MailboxList { //@cond PRIVATE kmime_mk_trivial_ctor( SingleMailbox ) //@endcond protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(SingleMailbox) }; class AddressListPrivate; /** Base class for headers that deal with (possibly multiple) addresses, allowing groups. Note: Groups are parsed but not represented in the API yet. All addresses in groups are listed as if they would not be part of a group. @todo Add API for groups? @see RFC 2822, section 3.4 */ class KMIME_EXPORT AddressList : public Address { //@cond PRIVATE kmime_mk_trivial_ctor( AddressList ) kmime_mk_dptr_ctor( AddressList ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Adds an address to this header. @param mbox A Mailbox object specifying the address. */ void addAddress( const Types::Mailbox &mbox ); /** Adds an address to this header. @param address The actual email address, with or without angle brackets. @param displayName An optional name associated with the address. */ void addAddress( const QByteArray &address, const QString &displayName = QString() ); /** Returns a list of all addresses in this header, regardless of groups. */ QList addresses() const; /** Returns a list of all display names associated with the addresses in this header. An empty entry is added for addresses that don't have a display name. */ QStringList displayNames() const; /** Returns a list of assembled display name / address strings of the following form: "Display Name <address>". These are unicode strings without any transport encoding, ie. they are only suitable for displaying. */ QStringList prettyAddresses() const; /** Returns a list of mailboxes listed in this header. */ Types::Mailbox::List mailboxes() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(AddressList) }; class IdentPrivate; /** Base class for headers which deal with a list of msg-id's. @see RFC 2822, section 3.6.4 */ class KMIME_EXPORT Ident : public Address { //@cond PRIVATE kmime_mk_trivial_ctor( Ident ) kmime_mk_dptr_ctor( Ident ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the list of identifiers contained in this header. Note: - Identifiers are not enclosed in angle-brackets. - Identifiers are listed in the same order as in the header. */ QList identifiers() const; /** Appends a new identifier to this header. @param id The identifier to append, with or without angle-brackets. */ void appendIdentifier( const QByteArray &id ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(Ident) }; class SingleIdentPrivate; /** Base class for headers which deal with a single msg-id. @see RFC 2822, section 3.6.4 */ class KMIME_EXPORT SingleIdent : public Ident { //@cond PRIVATE kmime_mk_trivial_ctor( SingleIdent ) //@endcond public: /** Returns the identifier contained in this header. Note: The identifiers is not enclosed in angle-brackets. */ QByteArray identifier() const; /** Sets the identifier. @param id The new identifier with or without angle-brackets. */ void setIdentifier( const QByteArray &id ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(SingleIdent) }; class TokenPrivate; /** Base class for headers which deal with a single atom. */ class KMIME_EXPORT Token : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( Token ) kmime_mk_dptr_ctor( Token ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the token. */ QByteArray token() const; /** Sets the token to @p t, */ void setToken( const QByteArray &t ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(Token) }; class PhraseListPrivate; /** Base class for headers containing a list of phrases. */ class KMIME_EXPORT PhraseList : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( PhraseList ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns the list of phrases contained in this header. */ QStringList phrases() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(PhraseList) }; class DotAtomPrivate; /** Base class for headers containing a dot atom. */ class KMIME_EXPORT DotAtom : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( DotAtom ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(DotAtom) }; class ParametrizedPrivate; /** Base class for headers containing a parameter list such as "Content-Type". */ class KMIME_EXPORT Parametrized : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor( Parametrized ) kmime_mk_dptr_ctor( Parametrized ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual bool isEmpty() const; virtual void clear(); /** Returns the value of the specified parameter. @param key The parameter name. */ QString parameter( const QString &key ) const; /** Sets the parameter @p key to @p value. @param key The parameter name. @param value The new value for @p key. */ void setParameter( const QString &key, const QString &value ); protected: virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(Parametrized) }; } // namespace Generics // // // INCOMPATIBLE, GSTRUCTURED-BASED FIELDS: // // class ReturnPathPrivate; /** Represents the Return-Path header field. @see RFC 2822, section 3.6.7 */ class KMIME_EXPORT ReturnPath : public Generics::Address { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ReturnPath ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(ReturnPath) }; // Address et al.: // rfc(2)822 headers: /** Represent a "From" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT From : public Generics::MailboxList { kmime_mk_trivial_ctor_with_name( From ) }; /** Represents a "Sender" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT Sender : public Generics::SingleMailbox { kmime_mk_trivial_ctor_with_name( Sender ) }; /** Represents a "To" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT To : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( To ) }; /** Represents a "Cc" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT Cc : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( Cc ) }; /** Represents a "Bcc" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT Bcc : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( Bcc ) }; /** Represents a "ReplyTo" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT ReplyTo : public Generics::AddressList { kmime_mk_trivial_ctor_with_name( ReplyTo ) }; class MailCopiesToPrivate; /** Represents a "Mail-Copies-To" header. @see http://www.newsreaders.com/misc/mail-copies-to.html */ class KMIME_EXPORT MailCopiesTo : public Generics::AddressList { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( MailCopiesTo ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns true if a mail copy was explicitly requested. */ bool alwaysCopy() const; /** Sets the header to "poster". */ void setAlwaysCopy(); /** Returns true if a mail copy was explicitly denied. */ bool neverCopy() const; /** Sets the header to "never". */ void setNeverCopy(); protected: virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(MailCopiesTo) }; class ContentTransferEncodingPrivate; /** Represents a "Content-Transfer-Encoding" header. @see RFC 2045, section 6. */ class KMIME_EXPORT ContentTransferEncoding : public Generics::Token { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentTransferEncoding ) //@endcond public: virtual void clear(); /** Returns the encoding specified in this header. */ contentEncoding encoding() const; /** Sets the encoding to @p e. */ void setEncoding( contentEncoding e ); + /** + Returns whether the Content containing this header is already decoded. + */ + // KDE5: rename to isDecoded(). bool decoded() const; + /** + Set whether the Content containing this header is already decoded. + For instance, if you fill your Content with already-encoded base64 data, + you will want to setDecoded( false ). + */ void setDecoded( bool decoded = true ); + /** + Returns whether the Content containing this header needs to be encoded + (i.e., if decoded() is true and encoding() is base64 or quoted-printable). + */ bool needToEncode() const; protected: virtual bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(ContentTransferEncoding) }; /** Represents a "Keywords" header. @see RFC 2822, section 3.6.5. */ class KMIME_EXPORT Keywords : public Generics::PhraseList { kmime_mk_trivial_ctor_with_name( Keywords ) }; // DotAtom: /** Represents a "MIME-Version" header. @see RFC 2045, section 4. */ class KMIME_EXPORT MIMEVersion : public Generics::DotAtom { kmime_mk_trivial_ctor_with_name( MIMEVersion ) }; // Ident: /** Represents a "Message-ID" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT MessageID : public Generics::SingleIdent { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( MessageID ) //@endcond public: /** Generate a message identifer. @param fqdn A fully qualified domain name. */ void generate( const QByteArray &fqdn ); }; /** Represents a "Content-ID" header. */ class KMIME_EXPORT ContentID : public Generics::SingleIdent { kmime_mk_trivial_ctor_with_name( ContentID ) }; /** Represents a "Supersedes" header. */ class KMIME_EXPORT Supersedes : public Generics::SingleIdent { kmime_mk_trivial_ctor_with_name( Supersedes ) }; /** Represents a "In-Reply-To" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT InReplyTo : public Generics::Ident { kmime_mk_trivial_ctor_with_name( InReplyTo ) }; /** Represents a "References" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT References : public Generics::Ident { kmime_mk_trivial_ctor_with_name( References ) }; class ContentTypePrivate; /** Represents a "Content-Type" header. @see RFC 2045, section 5. */ class KMIME_EXPORT ContentType : public Generics::Parametrized { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentType ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the mimetype. */ QByteArray mimeType() const; /** Returns the media type (first part of the mimetype). */ QByteArray mediaType() const; /** Returns the mime sub-type (second part of the mimetype). */ QByteArray subType() const; /** Sets the mimetype and clears already existing parameters. @param mimeType The new mimetype. */ void setMimeType( const QByteArray &mimeType ); /** Tests if the media type equals @p mediatype. */ bool isMediatype( const char *mediatype ) const; /** Tests if the mime sub-type equals @p subtype. */ bool isSubtype( const char *subtype ) const; /** Returns true if the associated MIME entity is a text. */ bool isText() const; /** Returns true if the associated MIME entity is a plain text. */ bool isPlainText() const; /** Returns true if the associated MIME entity is a HTML file. */ bool isHTMLText() const; /** Returns true if the associated MIME entity is an image. */ bool isImage() const; /** Returns true if the associated MIME entity is a mulitpart container. */ bool isMultipart() const; /** Returns true if the associated MIME entity contains partial data. @see partialNumber(), partialCount() */ bool isPartial() const; /** Returns the charset for the associated MIME entity. */ QByteArray charset() const; /** Sets the charset. */ void setCharset( const QByteArray &s ); /** Returns the boundary (for mulitpart containers). */ QByteArray boundary() const; /** Sets the mulitpart container boundary. */ void setBoundary( const QByteArray &s ); /** Returns the name of the associated MIME entity. */ QString name() const; /** Sets the name to @p s using charset @p cs. */ void setName( const QString &s, const QByteArray &cs ); /** Returns the identifier of the associated MIME entity. */ QByteArray id() const; /** Sets the identifier. */ void setId( const QByteArray &s ); /** Returns the position of this part in a multi-part set. @see isPartial(), partialCount() */ int partialNumber() const; /** Returns the total number of parts in a multi-part set. @see isPartial(), partialNumber() */ int partialCount() const; /** Sets parameters of a partial MIME entity. @param total The total number of entities in the multi-part set. @param number The number of this entity in a multi-part set. */ void setPartialParams( int total, int number ); // TODO: document contentCategory category() const; void setCategory( contentCategory c ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE(ContentType) }; class ContentDispositionPrivate; /** Represents a "Content-Disposition" header. @see RFC 2183 */ class KMIME_EXPORT ContentDisposition : public Generics::Parametrized { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( ContentDisposition ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual bool isEmpty() const; virtual void clear(); /** Returns the content disposition. */ contentDisposition disposition() const; /** Sets the content disposition. @param disp The new content disposition. */ void setDisposition( contentDisposition disp ); /** Returns the suggested filename for the associated MIME part. This is just a convenience function, it is equivalent to calling parameter( "filename" ); */ QString filename() const; /** Sets the suggested filename for the associated MIME part. This is just a convenience function, it is equivalent to calling setParameter( "filename", filename ); @param filename The filename. */ void setFilename( const QString &filename ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF=false ); private: Q_DECLARE_PRIVATE( ContentDisposition ) }; // // // COMPATIBLE GUNSTRUCTURED-BASED FIELDS: // // class GenericPrivate; /** Represents an arbitrary header, that can contain any header-field. Adds a type over Unstructured. @see Unstructured */ class KMIME_EXPORT Generic : public Generics::Unstructured { public: Generic(); Generic( const char *t ); Generic( const char *t, Content *p ); Generic( const char *t, Content *p, const QByteArray &s ); Generic( const char *t, Content *p, const QString &s, const QByteArray &cs ); ~Generic(); virtual void clear(); + virtual Base *clone() const; + virtual bool isEmpty() const; virtual const char *type() const; void setType( const char *type ); private: Q_DECLARE_PRIVATE( Generic ) }; /** Represents a "Subject" header. @see RFC 2822, section 3.6.5. */ class KMIME_EXPORT Subject : public Generics::Unstructured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Subject ) //@endcond public: bool isReply() const; }; /** Represents a "Organization" header. */ class KMIME_EXPORT Organization : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( Organization ) }; /** Represents a "Content-Description" header. */ class KMIME_EXPORT ContentDescription : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( ContentDescription ) }; /** Represents a "Content-Location" header. @since 4.2 */ class KMIME_EXPORT ContentLocation : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( ContentLocation ) }; class ControlPrivate; /** Represents a "Control" header. @see RFC 1036, section 3. */ class KMIME_EXPORT Control : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Control ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the control message type. */ QByteArray controlType() const; /** Returns the control message parameter. */ QByteArray parameter() const; /** Returns true if this is a cancel control message. @see RFC 1036, section 3.1. */ bool isCancel() const; /** Changes this header into a cancel control message for the given message-id. @param msgid The message-id of the article that should be canceled. */ void setCancel( const QByteArray &msgid ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE(Control) }; class DatePrivate; /** Represents a "Date" header. @see RFC 2822, section 3.3. */ class KMIME_EXPORT Date : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Date ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void clear(); virtual bool isEmpty() const; /** Returns the date contained in this header. */ KDateTime dateTime() const; /** Sets the date. */ void setDateTime( const KDateTime &dt ); /** Returns the age of the message. */ int ageInDays() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE( Date ) }; class NewsgroupsPrivate; /** Represents a "Newsgroups" header. @see RFC 1036, section 2.1.3. */ class KMIME_EXPORT Newsgroups : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Newsgroups ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual void fromUnicodeString( const QString &s, const QByteArray &b ); virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns the list of newsgroups. */ QList groups() const; /** Sets the newsgroup list. */ void setGroups( const QList &groups ); /** Returns true if this message has been cross-posted, i.e. if it has been posted to multiple groups. */ bool isCrossposted() const; protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE( Newsgroups ) }; /** Represents a "Followup-To" header. @see RFC 1036, section 2.2.3. */ class KMIME_EXPORT FollowUpTo : public Newsgroups { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( FollowUpTo ) //@endcond }; class LinesPrivate; /** Represents a "Lines" header. @see RFC 1036, section 2.2.12. */ class KMIME_EXPORT Lines : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name( Lines ) //@endcond public: virtual QByteArray as7BitString( bool withHeaderType = true ) const; virtual QString asUnicodeString() const; virtual void clear(); virtual bool isEmpty() const; /** Returns the number of lines, undefined if isEmpty() returns true. */ int numberOfLines() const; /** Sets the number of lines to @p lines. */ void setNumberOfLines( int lines ); protected: bool parse( const char* &scursor, const char *const send, bool isCRLF = false ); private: Q_DECLARE_PRIVATE( Lines ) }; /** Represents a "User-Agent" header. */ class KMIME_EXPORT UserAgent : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name( UserAgent ) }; } //namespace Headers } //namespace KMime // undefine code generation macros again #undef kmime_mk_trivial_ctor #undef kmime_mk_dptr_ctor #undef kmime_mk_trivial_ctor_with_name #endif // __KMIME_HEADERS_H__ diff --git a/kmime/kmime_message.cpp b/kmime/kmime_message.cpp index 97e8ed1aa..95da65172 100644 --- a/kmime/kmime_message.cpp +++ b/kmime/kmime_message.cpp @@ -1,295 +1,161 @@ /* kmime_message.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmime_message.h" #include "kmime_message_p.h" #include "kmime_util_p.h" using namespace KMime; namespace KMime { -Message::Message() : Content( new MessagePrivate( this ) ) {} +Message::Message() + : Content( new MessagePrivate( this ) ) +{ +} -Message::Message(MessagePrivate * d) : Content( d ) {} +Message::Message(MessagePrivate * d) + : Content( d ) +{ +} Message::~Message() -{} +{ +} void Message::parse() { - Q_D(Message); + // KDE5: remove this virtual reimplementation. Content::parse(); - - QByteArray raw; - if ( !( raw = rawHeader( d->subject.type() ) ).isEmpty() ) - d->subject.from7BitString( raw ); - - if ( !( raw = rawHeader( d->date.type() ) ).isEmpty() ) - d->date.from7BitString( raw ); } QByteArray Message::assembleHeaders() { - Q_D(Message); - Headers::Base *h; - QByteArray newHead; - KMime::Message m; - m.setContent( d->fullContent() ); - m.parse(); - - //Message-ID - if ( ( h = messageID( false ) ) != 0 && !h->isEmpty() && h->as7BitString() != m.messageID()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //From - h = from(); // "From" is mandatory - if ( !h->isEmpty() && h->as7BitString() != m.from()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Subject - h = subject(); // "Subject" is mandatory - if ( !h->isEmpty() && h->as7BitString() != m.subject()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //To - if ( ( h = to( false )) != 0 && !h->isEmpty() && h->as7BitString() != m.to()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Cc - if ( ( h = cc( false )) != 0 && !h->isEmpty() && h->as7BitString() != m.cc()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Reply-To - if ( ( h = replyTo( false )) != 0 && !h->isEmpty() && h->as7BitString() != m.replyTo()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Date - h = date(); // "Date" is mandatory - if ( !h->isEmpty() && h->as7BitString() != m.date()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //References - if ( ( h = references( false )) != 0 && !h->isEmpty() && h->as7BitString() != m.references()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Organization - if ( ( h = organization( false )) != 0 && !h->isEmpty() && h->as7BitString() != m.organization()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } + // Create the mandatory fields (RFC5322) if they do not exist already. + date( true ); + from( true ); - //UserAgent - if ( ( h = userAgent( false )) != 0 && !h->isEmpty() && h->as7BitString() != m.userAgent()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - // In-Reply-To - if ( ( h = inReplyTo( false ) ) != 0 && !h->isEmpty() && h->as7BitString() != m.inReplyTo()->as7BitString() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } + // Make sure the mandatory MIME-Version field (RFC2045) is present and valid. + Headers::MIMEVersion *mimeVersion = header( true ); + mimeVersion->from7BitString( "1.0" ); - //Mime-Version - Headers::Base *mimeHeader = m.headerByType("MIME-Version"); - QByteArray oldMimeVersion; - if ( mimeHeader ) - oldMimeVersion = mimeHeader->as7BitString(); - if ( oldMimeVersion != "MIME-Version: 1.0" ) { - newHead += "MIME-Version: 1.0\n"; - KMime::removeHeader( d->head, "MIME-Version" ); - } - - QByteArray newContentHead = Content::assembleHeaders(); - - if ( newHead.isEmpty() && newContentHead == d->head ) { //no header was changed - return d->head; - } else { - return newHead + newContentHead; - } + // Assemble all header fields. + return Content::assembleHeaders(); } void Message::clear() { - Q_D(Message); - d->subject.clear(); - d->date.clear(); + // KDE5: remove this virtual reimplementation. Content::clear(); } Headers::Base *Message::getHeaderByType( const char *type ) { - return headerByType( type ); + // KDE5: remove this virtual reimplementation. + return headerByType( type ); } Headers::Base *Message::headerByType( const char *type ) { - Q_D(Message); - if ( strcasecmp( "Subject", type ) == 0 ) { - if ( d->subject.isEmpty() ) { - return 0; - } else { - return &d->subject; - } - } - else if ( strcasecmp("Date", type ) == 0 ){ - if ( d->date.isEmpty() ) { - return 0; - } else { - return &d->date; - } - } else { - return Content::headerByType( type ); - } + // KDE5: remove this virtual reimplementation. + return Content::headerByType( type ); } void Message::setHeader( Headers::Base *h ) { - Q_D(Message); - bool del = true; - if ( h->is( "Subject" ) ) { - d->subject.fromUnicodeString( h->asUnicodeString(), h->rfc2047Charset() ); - } else if ( h->is( "Date" ) ) { - d->date.setDateTime( ( static_cast( h ) )->dateTime() ); - } else { - del = false; - Content::setHeader( h ); - } - - if ( del ) delete h; + // KDE5: remove this virtual reimplementation. + Content::setHeader( h ); } bool Message::removeHeader( const char *type ) { - Q_D(Message); - if ( strcasecmp( "Subject", type ) == 0 ) { - d->subject.clear(); - } else if ( strcasecmp( "Date", type ) == 0 ) { - d->date.clear(); - } else { - return Content::removeHeader( type ); - } - - return true; -} - -Headers::Subject *Message::subject( bool create ) -{ - Q_D( Message ); - if ( !create && d->subject.isEmpty() ) { - return 0; - } - return &d->subject; -} - -Headers::Date *Message::date( bool create ) -{ - Q_D( Message ); - if ( !create && d->date.isEmpty() ) { - return 0; - } - return &d->date; + // KDE5: remove this virtual reimplementation. + return Content::removeHeader( type ); } bool Message::isTopLevel() const { return true; } Content *Message::mainBodyPart( const QByteArray &type ) { KMime::Content *c = this; while ( c ) { // not a multipart message if ( !c->contentType()->isMultipart() ) { if ( c->contentType()->mimeType() == type || type.isEmpty() ) { return c; } return 0; } // empty multipart if ( c->contents().count() == 0 ) { return 0; } // multipart/alternative if ( c->contentType()->subType() == "alternative" ) { if ( type.isEmpty() ) { return c->contents().first(); } foreach ( Content *c1, c->contents() ) { if ( c1->contentType()->mimeType() == type ) { return c1; } } return 0; } c = c->contents().first(); } return 0; } // @cond PRIVATE -#define kmime_mk_header_accessor( header, method ) \ -Headers::header *Message::method( bool create ) { \ - Headers::header *p = 0; \ - return headerInstance( p, create ); \ +#define kmime_mk_header_accessor( type, method ) \ +Headers::type *Message::method( bool create ) { \ + return header( create ); \ } kmime_mk_header_accessor( MessageID, messageID ) +kmime_mk_header_accessor( Subject, subject ) +kmime_mk_header_accessor( Date, date ) kmime_mk_header_accessor( Organization, organization ) kmime_mk_header_accessor( From, from ) kmime_mk_header_accessor( ReplyTo, replyTo ) kmime_mk_header_accessor( To, to ) kmime_mk_header_accessor( Cc, cc ) kmime_mk_header_accessor( Bcc, bcc ) kmime_mk_header_accessor( References, references ) kmime_mk_header_accessor( UserAgent, userAgent ) kmime_mk_header_accessor( InReplyTo, inReplyTo ) kmime_mk_header_accessor( Sender, sender ) #undef kmime_mk_header_accessor // @endcond } diff --git a/kmime/kmime_message_p.h b/kmime/kmime_message_p.h index 668f3de89..ad33d9703 100644 --- a/kmime/kmime_message_p.h +++ b/kmime/kmime_message_p.h @@ -1,48 +1,43 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_MESSAGE_P_H #define KMIME_MESSAGE_P_H #include "kmime_content_p.h" // @cond PRIVATE namespace KMime { class MessagePrivate : public ContentPrivate { public: MessagePrivate( Message *q ) : ContentPrivate( q ) { - subject.setParent( q ); - date.setParent( q ); } - KMime::Headers::Subject subject; - KMime::Headers::Date date; - Q_DECLARE_PUBLIC(Message) }; } // @endcond #endif diff --git a/kmime/kmime_newsarticle.cpp b/kmime/kmime_newsarticle.cpp index 6f030f3cf..39740bf69 100644 --- a/kmime/kmime_newsarticle.cpp +++ b/kmime/kmime_newsarticle.cpp @@ -1,181 +1,112 @@ /* kmime_newsarticle.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmime_newsarticle.h" #include "kmime_message_p.h" #include "kmime_util_p.h" using namespace KMime; namespace KMime { class NewsArticlePrivate : public MessagePrivate { public: NewsArticlePrivate( NewsArticle *q ) : MessagePrivate( q ) { - lines.setParent( q ); } - KMime::Headers::Lines lines; - Q_DECLARE_PUBLIC(NewsArticle) }; -NewsArticle::NewsArticle() : Message( new NewsArticlePrivate( this ) ) {} +NewsArticle::NewsArticle() + : Message( new NewsArticlePrivate( this ) ) +{ +} -NewsArticle::~NewsArticle() {} +NewsArticle::~NewsArticle() +{ +} void NewsArticle::parse() { - Q_D(NewsArticle); + // KDE5: remove this virtual reimplementation. Message::parse(); - - QByteArray raw; - - if ( !( raw = rawHeader( d->lines.type() ) ).isEmpty() ) - d->lines.from7BitString( raw ); } QByteArray NewsArticle::assembleHeaders() { - Q_D(NewsArticle); - Headers::Base *h; - QByteArray newHead; - - //Control - if ( ( h = control( false ) ) != 0 && !h->isEmpty() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Supersedes - if ( ( h = supersedes( false ) ) != 0 && !h->isEmpty() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Newsgroups - if ( ( h = newsgroups( false ) ) != 0 && !h->isEmpty() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Followup-To - if ( ( h = followUpTo( false ) ) != 0 && !h->isEmpty() ) { - newHead+=h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Mail-Copies-To - if ( ( h = mailCopiesTo( false ) ) != 0 && !h->isEmpty() ) { - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - } - - //Lines - h = lines(); // "Lines" is mandatory - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); - - newHead += Message::assembleHeaders(); - return newHead; + // Create the mandatory Lines: field. + lines( true ); + + // Assemble all header fields. + return Message::assembleHeaders(); } void NewsArticle::clear() { - Q_D(NewsArticle); - d->lines.clear(); + // KDE5: remove this virtual reimplementation. Message::clear(); } Headers::Base * NewsArticle::getHeaderByType( const char *type ) { - return headerByType( type ); + // KDE5: remove this virtual reimplementation. + return headerByType( type ); } Headers::Base * NewsArticle::headerByType( const char *type ) { - Q_D(NewsArticle); - if ( strcasecmp( "Lines", type ) == 0 ) { - if ( d->lines.isEmpty() ) { - return 0; - } else { - return &d->lines; - } - } else { - return Message::headerByType( type ); - } + // KDE5: remove this virtual reimplementation. + return Message::headerByType( type ); } void NewsArticle::setHeader( Headers::Base *h ) { - Q_D(NewsArticle); - bool del = true; - if ( h->is( "Lines" ) ) { - d->lines.setNumberOfLines( (static_cast(h))->numberOfLines() ); - } else { - del = false; - Message::setHeader( h ); - } - - if ( del ) delete h; + // KDE5: remove this virtual reimplementation. + Message::setHeader( h ); } bool NewsArticle::removeHeader( const char *type ) { - Q_D(NewsArticle); - if ( strcasecmp( "Lines", type ) == 0 ) { - d->lines.clear(); - } else { - return Message::removeHeader( type ); - } - - return true; -} - -Headers::Lines* NewsArticle::lines(bool create) -{ - Q_D(NewsArticle); - if ( !create && d->lines.isEmpty() ) - return 0; - return &d->lines; + // KDE5: remove this virtual reimplementation. + return Message::removeHeader( type ); } // @cond PRIVATE -#define kmime_mk_header_accessor( header, method ) \ -Headers::header* NewsArticle::method( bool create ) { \ - Headers::header *p = 0; \ - return headerInstance( p, create ); \ +#define kmime_mk_header_accessor( type, method ) \ +Headers::type* NewsArticle::method( bool create ) { \ + return header( create ); \ } kmime_mk_header_accessor( Control, control ) +kmime_mk_header_accessor( Lines, lines ) kmime_mk_header_accessor( Supersedes, supersedes ) kmime_mk_header_accessor( MailCopiesTo, mailCopiesTo ) kmime_mk_header_accessor( Newsgroups, newsgroups ) kmime_mk_header_accessor( FollowUpTo, followUpTo ) #undef kmime_mk_header_accessor // @endcond } // namespace KMime diff --git a/kmime/tests/CMakeLists.txt b/kmime/tests/CMakeLists.txt index a2e69cdb1..5aa054928 100644 --- a/kmime/tests/CMakeLists.txt +++ b/kmime/tests/CMakeLists.txt @@ -1,72 +1,73 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories(${CMAKE_SOURCE_DIR}/kmime) # convenience macro to add libkmime qtestlib qtgui unit-tests macro(add_kmime_test _source) set(_test ${_source}) get_filename_component(_name ${_source} NAME_WE) kde4_add_unit_test(${_name} TESTNAME kmime-${_name} ${_test}) target_link_libraries(${_name} kmime ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} ${KDE4_KDEUI_LIBS} ) endmacro(add_kmime_test) ########### next target ############### if(HAVE_GETOPT_H) set(test_kmime_header_parsing_SRCS test_kmime_header_parsing.cpp ) kde4_add_executable(test_kmime_header_parsing TEST ${test_kmime_header_parsing_SRCS}) target_link_libraries(test_kmime_header_parsing kmime ${KDE4_KDECORE_LIBS} ) endif(HAVE_GETOPT_H) ########### next target ############### set(test_charfreq_SRCS test_charfreq.cpp ) kde4_add_executable(test_charfreq TEST ${test_charfreq_SRCS}) target_link_libraries(test_charfreq kmime ${KDE4_KDECORE_LIBS} ) ########### next target ############### if(HAVE_GETOPT_H) set(test_mdn_SRCS test_mdn.cpp ) kde4_add_executable(test_mdn TEST ${test_mdn_SRCS}) target_link_libraries(test_mdn kmime ${KDE4_KDECORE_LIBS} ) endif(HAVE_GETOPT_H) ########### next target ############### set(test_dates_SRCS test_dates.cpp ) kde4_add_executable(test_dates TEST ${test_dates_SRCS}) target_link_libraries(test_dates kmime ${KDE4_KDECORE_LIBS} ) ########### next target ############### if(HAVE_GETOPT_H) set(test_kmime_codec_SRCS test_kmime_codec.cpp ) kde4_add_executable(test_kmime_codec TEST ${test_kmime_codec_SRCS}) target_link_libraries(test_kmime_codec kmime ${KDE4_KDECORE_LIBS} ) endif(HAVE_GETOPT_H) # qtestlib unit tests add_kmime_test(rfc2047test.cpp) add_kmime_test(kmime_util_test.cpp) add_kmime_test(contentindextest.cpp) add_kmime_test(kmime_charfreq_test.cpp) -add_kmime_test(kmime_content_test.cpp) add_kmime_test(headertest.cpp) +add_kmime_test(kmime_headerfactorytest.cpp) +add_kmime_test(kmime_content_test.cpp) add_kmime_test(kmime_message_test.cpp) diff --git a/kmime/tests/headertest.cpp b/kmime/tests/headertest.cpp index 099180532..49508951a 100644 --- a/kmime/tests/headertest.cpp +++ b/kmime/tests/headertest.cpp @@ -1,756 +1,822 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "headertest.h" + +#include #include #include using namespace KMime; using namespace KMime::Headers; using namespace KMime::Headers::Generics; // the following test cases are taken from KDE mailinglists, bug reports, RFC 2045, // RFC 2183 and RFC 2822, Appendix A QTEST_KDEMAIN( HeaderTest, NoGUI ) void HeaderTest::testIdentHeader() { // empty header Headers::Generics::Ident* h = new Headers::Generics::Ident(); QVERIFY( h->isEmpty() ); // parse single identifier h->from7BitString( QByteArray( "<1162746587.784559.5038.nullmailer@svn.kde.org>" ) ); QCOMPARE( h->identifiers().count(), 1 ); QCOMPARE( h->identifiers().first(), QByteArray( "1162746587.784559.5038.nullmailer@svn.kde.org" ) ); QCOMPARE( h->asUnicodeString(), QString("<1162746587.784559.5038.nullmailer@svn.kde.org>") ); QVERIFY( !h->isEmpty() ); // clearing a header h->clear(); QVERIFY( h->isEmpty() ); QVERIFY( h->identifiers().isEmpty() ); delete h; // parse multiple identifiers h = new Headers::Generics::Ident(); h->from7BitString( QByteArray( "<1234@local.machine.example> <3456@example.net>" ) ); QCOMPARE( h->identifiers().count(), 2 ); QList ids = h->identifiers(); QCOMPARE( ids.takeFirst(), QByteArray( "1234@local.machine.example" ) ); QCOMPARE( ids.first(), QByteArray( "3456@example.net" ) ); delete h; // parse multiple identifiers with folded headers h = new Headers::Generics::Ident(); h->from7BitString( QByteArray( "<1234@local.machine.example>\n <3456@example.net>" ) ); QCOMPARE( h->identifiers().count(), 2 ); ids = h->identifiers(); QCOMPARE( ids.takeFirst(), QByteArray( "1234@local.machine.example" ) ); QCOMPARE( ids.first(), QByteArray( "3456@example.net" ) ); // appending of new identifiers (with and without angle-brackets) h->appendIdentifier( "" ); h->appendIdentifier( "78910@example.net" ); QCOMPARE( h->identifiers().count(), 4 ); // assemble the final header QCOMPARE( h->as7BitString( false ), QByteArray("<1234@local.machine.example> <3456@example.net> <78910@example.net>") ); } void HeaderTest::testAddressListHeader() { // empty header Headers::Generics::AddressList *h = new Headers::Generics::AddressList(); QVERIFY( h->isEmpty() ); // parse single simple address h->from7BitString( "joe@where.test" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("joe@where.test") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString() ); QCOMPARE( h->prettyAddresses().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("joe@where.test") ); // clearing a header h->clear(); QVERIFY( h->isEmpty() ); delete h; // parsing and re-assembling a single address with display name h = new Headers::Generics::AddressList(); h->from7BitString( "Pete " ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "pete@silly.example" ) ); QCOMPARE( h->displayNames().first(), QString("Pete") ); QCOMPARE( h->prettyAddresses().first(), QString("Pete ") ); QCOMPARE( h->as7BitString( false ), QByteArray("Pete ") ); delete h; // parsing a single address with legacy comment style display name h = new Headers::Generics::AddressList(); h->from7BitString( "jdoe@machine.example (John Doe)" ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "jdoe@machine.example" ) ); QCOMPARE( h->displayNames().first(), QString("John Doe") ); QCOMPARE( h->prettyAddresses().first(), QString("John Doe ") ); delete h; // parsing and re-assembling list of diffrent addresses h = new Headers::Generics::AddressList(); h->from7BitString( "Mary Smith , jdoe@example.org, Who? " ); QCOMPARE( h->addresses().count(), 3 ); QStringList names = h->displayNames(); QCOMPARE( names.takeFirst(), QString("Mary Smith") ); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString("Who?") ); QCOMPARE( h->as7BitString( false ), QByteArray("Mary Smith , jdoe@example.org, Who? ") ); delete h; // same again with some interessting quoting h = new Headers::Generics::AddressList(); h->from7BitString( "\"Joe Q. Public\" , , \"Giant; \\\"Big\\\" Box\" " ); QCOMPARE( h->addresses().count(), 3 ); names = h->displayNames(); QCOMPARE( names.takeFirst(), QString("Joe Q. Public") ); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString("Giant; \"Big\" Box") ); QCOMPARE( h->as7BitString( false ), QByteArray("\"Joe Q. Public\" , boss@nil.test, \"Giant; \\\"Big\\\" Box\" ") ); delete h; // a display name with non-latin1 content h = new Headers::Generics::AddressList(); h->from7BitString( "Ingo =?iso-8859-15?q?Kl=F6cker?= " ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "kloecker@kde.org" ) ); QCOMPARE( h->displayNames().first(), QString::fromUtf8("Ingo Klöcker") ); QCOMPARE( h->asUnicodeString(), QString::fromUtf8("Ingo Klöcker ") ); QCOMPARE( h->as7BitString( false ), QByteArray("Ingo =?ISO-8859-1?Q?Kl=F6cker?= ") ); delete h; // again, this time legacy style h = new Headers::Generics::AddressList(); h->from7BitString( "kloecker@kde.org (Ingo =?iso-8859-15?q?Kl=F6cker?=)" ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "kloecker@kde.org" ) ); QCOMPARE( h->displayNames().first(), QString::fromUtf8("Ingo Klöcker") ); delete h; // parsing a empty group h = new Headers::Generics::AddressList(); h->from7BitString( "Undisclosed recipients:;" ); QCOMPARE( h->addresses().count(), 0 ); delete h; // parsing and re-assembling a address list with a group h = new Headers::Generics::AddressList(); h->from7BitString( "A Group:Chris Jones ,joe@where.test,John ;" ); QCOMPARE( h->addresses().count(), 3 ); names = h->displayNames(); QCOMPARE( names.takeFirst(), QString("Chris Jones") ); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString("John") ); QCOMPARE( h->as7BitString( false ), QByteArray("Chris Jones , joe@where.test, John ") ); delete h; // modifying a header h = new Headers::Generics::AddressList(); h->from7BitString( "John " ); h->addAddress( "", QString::fromUtf8("Ingo Klöcker") ); h->addAddress( "c@a.test" ); QCOMPARE( h->addresses().count(), 3 ); QCOMPARE( h->asUnicodeString(), QString::fromUtf8("John , Ingo Klöcker , c@a.test") ); QCOMPARE( h->as7BitString( false ), QByteArray("John , Ingo =?ISO-8859-1?Q?Kl=F6cker?= , c@a.test") ); delete h; // parsing from utf-8 h = new Headers::Generics::AddressList(); h->fromUnicodeString( QString::fromUtf8("Ingo Klöcker "), "utf-8" ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray( "kloecker@kde.org" ) ); QCOMPARE( h->displayNames().first(), QString::fromUtf8("Ingo Klöcker") ); delete h; // based on bug #137033, a header broken in various ways: ';' as list separator, // unquoted '.' in display name h = new Headers::Generics::AddressList(); h->from7BitString( "Vice@censored.serverkompetenz.net,\n President@mail2.censored.net;\"Int\\\\\\\\\\\\\\\\\\\\'l\" Lotto Commission. " ); QCOMPARE( h->addresses().count(), 3 ); names = h->displayNames(); QCOMPARE( names.takeFirst(), QString() ); QCOMPARE( names.takeFirst(), QString() ); // there is an wrong ' ' after the name, but since the header is completely // broken we can be happy it parses at all... QCOMPARE( names.takeFirst(), QString("Int\\\\\\\\\\'l Lotto Commission. ") ); QList addrs = h->addresses(); QCOMPARE( addrs.takeFirst(), QByteArray("Vice@censored.serverkompetenz.net") ); QCOMPARE( addrs.takeFirst(), QByteArray("President@mail2.censored.net") ); QCOMPARE( addrs.takeFirst(), QByteArray("censored@yahoo.fr") ); delete h; // based on bug #102010, a display name containing '<' h = new Headers::Generics::AddressList( 0, QByteArray("\"|") ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("censored@censored.dy") ); QCOMPARE( h->displayNames().first(), QString("|as7BitString( false ), QByteArray("\"|") ); // based on bug #93790 (legacy display name with nested comments) h = new Headers::Generics::AddressList( 0, QByteArray("first.name@domain.tld (first name (nickname))") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString("first name (nickname)") ); QCOMPARE( h->as7BitString( false ), QByteArray("\"first name (nickname)\" ") ); delete h; // rfc 2047 encoding in quoted name (which is not allowed there) h = new Headers::Generics::AddressList(); h->from7BitString( QByteArray( "\"Ingo =?iso-8859-15?q?Kl=F6cker?=\" " ) ); QCOMPARE( h->mailboxes().count(), 1 ); QCOMPARE( h->asUnicodeString(), QString::fromUtf8( "Ingo =?iso-8859-15?q?Kl=F6cker?= " ) ); delete h; } void HeaderTest::testMailboxListHeader() { // empty header Headers::Generics::MailboxList *h = new Headers::Generics::MailboxList(); QVERIFY( h->isEmpty() ); // parse single simple address h->from7BitString( "joe_smith@where.test" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->mailboxes().count(), 1 ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("joe_smith@where.test") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString() ); QCOMPARE( h->prettyAddresses().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("joe_smith@where.test") ); // https://bugzilla.novell.com/show_bug.cgi?id=421057 (but apparently this was not the cause of the bug) h->from7BitString( "fr...@ce.sco (Francesco)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->mailboxes().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("Francesco ") ); delete h; } void HeaderTest::testSingleMailboxHeader() { // empty header Headers::Generics::SingleMailbox *h = new Headers::Generics::SingleMailbox(); QVERIFY( h->isEmpty() ); // parse single simple address h->from7BitString( "joe_smith@where.test" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->addresses().count(), 1 ); QCOMPARE( h->addresses().first(), QByteArray("joe_smith@where.test") ); QCOMPARE( h->displayNames().count(), 1 ); QCOMPARE( h->displayNames().first(), QString() ); QCOMPARE( h->prettyAddresses().count(), 1 ); QCOMPARE( h->prettyAddresses().first(), QString("joe_smith@where.test") ); delete h; } void HeaderTest::testMailCopiesToHeader() { Headers::MailCopiesTo *h; // empty header h = new Headers::MailCopiesTo(); QVERIFY( h->isEmpty() ); QVERIFY( !h->alwaysCopy() ); QVERIFY( !h->neverCopy() ); // set to always copy to poster h->setAlwaysCopy(); QVERIFY( !h->isEmpty() ); QVERIFY( h->alwaysCopy() ); QVERIFY( !h->neverCopy() ); QCOMPARE( h->as7BitString(), QByteArray( "Mail-Copies-To: poster" ) ); // set to never copy h->setNeverCopy(); QVERIFY( !h->isEmpty() ); QVERIFY( !h->alwaysCopy() ); QVERIFY( h->neverCopy() ); QCOMPARE( h->as7BitString(), QByteArray( "Mail-Copies-To: nobody" ) ); // clear header h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse copy to poster h = new MailCopiesTo( 0, "always" ); QVERIFY( h->addresses().isEmpty() ); QVERIFY( !h->isEmpty() ); QVERIFY( h->alwaysCopy() ); delete h; // parse never copy h = new MailCopiesTo( 0, "never" ); QVERIFY( h->addresses().isEmpty() ); QVERIFY( !h->isEmpty() ); QVERIFY( h->neverCopy() ); delete h; // parse address h = new MailCopiesTo( 0, "vkrause@kde.org" ); QVERIFY( !h->addresses().isEmpty() ); QVERIFY( h->alwaysCopy() ); QVERIFY( !h->neverCopy() ); QCOMPARE( h->as7BitString(), QByteArray( "Mail-Copies-To: vkrause@kde.org" ) ); delete h; } void HeaderTest::testParametrizedHeader() { Parametrized *h; // empty header h = new Parametrized(); QVERIFY( h->isEmpty() ); // add a parameter h->setParameter( "filename", "bla.jpg" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->parameter( "filename" ), QString( "bla.jpg" ) ); QCOMPARE( h->as7BitString( false ), QByteArray( "filename=\"bla.jpg\"" ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a parameter list h = new Parametrized( 0, "filename=genome.jpeg;\n modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" ); QCOMPARE( h->parameter( "filename" ), QString( "genome.jpeg" ) ); QCOMPARE( h->parameter( "modification-date" ), QString( "Wed, 12 Feb 1997 16:29:51 -0500" ) ); QCOMPARE( h->as7BitString( false ), QByteArray( "filename=\"genome.jpeg\"; modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"" ) ); delete h; // quoting of whitespaces in parameter value h = new Parametrized(); h->setParameter( "boundary", "simple boundary" ); QCOMPARE( h->as7BitString( false ), QByteArray( "boundary=\"simple boundary\"" ) ); delete h; // TODO: test RFC 2047 encoded values // TODO: test case-insensitive key-names } void HeaderTest::testContentDispositionHeader() { ContentDisposition *h; // empty header h = new ContentDisposition(); QVERIFY( h->isEmpty() ); // set some values h->setFilename( "test.jpg" ); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString( false ).isEmpty() ); h->setDisposition( CDattachment ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( false ), QByteArray( "attachment; filename=\"test.jpg\"" ) ); delete h; // parse parameter-less header h = new ContentDisposition( 0, "inline" ); QCOMPARE( h->disposition(), CDinline ); QVERIFY( h->filename().isEmpty() ); QCOMPARE( h->as7BitString( true ), QByteArray( "Content-Disposition: inline" ) ); delete h; // parse header with parameter h = new ContentDisposition( 0, "attachment; filename=genome.jpeg;\n modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"); QCOMPARE( h->disposition(), CDattachment ); QCOMPARE( h->filename(), QString( "genome.jpeg" ) ); delete h; // TODO: test for case-insensitive disposition value } void HeaderTest::testContentTypeHeader() { ContentType* h; // empty header h = new ContentType(); QVERIFY( h->isEmpty() ); // Empty content-type means text/plain (RFC 2045 §5.2) QVERIFY( h->isPlainText() ); QVERIFY( h->isText() ); // set a mimetype h->setMimeType( "text/plain" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->mimeType(), QByteArray( "text/plain" ) ); QCOMPARE( h->mediaType(), QByteArray("text") ); QCOMPARE( h->subType(), QByteArray("plain") ); QVERIFY( h->isText() ); QVERIFY( h->isPlainText() ); QVERIFY( !h->isMultipart() ); QVERIFY( !h->isPartial() ); QVERIFY( h->isMediatype( "text" ) ); QVERIFY( h->isSubtype( "plain" ) ); QCOMPARE( h->as7BitString( true ), QByteArray( "Content-Type: text/plain" ) ); // add some parameters h->setId( "bla" ); h->setCharset( "us-ascii" ); QCOMPARE( h->as7BitString( false ), QByteArray( "text/plain; charset=\"us-ascii\"; id=\"bla\"" ) ); // clear header h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a complete header h = new ContentType( 0, "text/plain; charset=us-ascii (Plain text)" ); QVERIFY( h->isPlainText() ); QCOMPARE( h->charset(), QByteArray( "us-ascii" ) ); delete h; // bug #136631 (name with rfc 2231 style parameter wrapping) h = new ContentType( 0, "text/plain;\n name*0=\"PIN_Brief_box1@xx.xxx.censored_Konfigkarte.confi\";\n name*1=\"guration.txt\"" ); QVERIFY( h->isPlainText() ); QCOMPARE( h->name(), QString( "PIN_Brief_box1@xx.xxx.censored_Konfigkarte.configuration.txt" ) ); delete h; } void HeaderTest::testTokenHeader() { Token *h; // empty header h = new Token(); QVERIFY( h->isEmpty() ); // set a token h->setToken( "bla" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( false ), QByteArray( "bla" ) ); // clear it again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a header h = new Token( 0, "value (comment)" ); QCOMPARE( h->token(), QByteArray("value") ); QCOMPARE( h->as7BitString( false ), QByteArray("value") ); delete h; } void HeaderTest::testContentTransferEncoding() { ContentTransferEncoding *h; // empty header h = new ContentTransferEncoding(); QVERIFY( h->isEmpty() ); // set an encoding h->setEncoding( CEbinary ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( true ), QByteArray("Content-Transfer-Encoding: binary") ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a header h = new ContentTransferEncoding( 0, "(comment) base64" ); QCOMPARE( h->encoding(), CEbase64 ); QCOMPARE( h->as7BitString( false ), QByteArray("base64") ); delete h; } void HeaderTest::testPhraseListHeader() { PhraseList *h; // empty header h = new PhraseList(); QVERIFY( h->isEmpty() ); delete h; // parse a simple phrase list h = new PhraseList( 0, "foo,\n bar" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->phrases().count(), 2 ); QStringList phrases = h->phrases(); QCOMPARE( phrases.takeFirst(), QString( "foo" ) ); QCOMPARE( phrases.takeFirst(), QString( "bar" ) ); QCOMPARE( h->as7BitString( false ), QByteArray("foo, bar") ); // clear header h->clear(); QVERIFY( h->isEmpty() ); delete h; // TODO: encoded/quoted phrases } void HeaderTest::testDotAtomHeader() { DotAtom *h; // empty header h = new DotAtom; QVERIFY( h->isEmpty() ); // parse a simple dot atom h->from7BitString( "1.0 (mime version)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->asUnicodeString(), QString( "1.0" ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // TODO: more complex atoms } void HeaderTest::testDateHeader() { Date *h; // empty header h = new Date(); QVERIFY( h->isEmpty() ); // parse a simple date h->from7BitString( "Fri, 21 Nov 1997 09:55:06 -0600" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1997, 11, 21 ) ); QCOMPARE( h->dateTime().time(), QTime( 9, 55, 6 ) ); QCOMPARE( h->dateTime().utcOffset(), -6 * 3600 ); QCOMPARE( h->as7BitString(), QByteArray( "Date: Fri, 21 Nov 1997 09:55:06 -0600" ) ); // clear it again h->clear(); QVERIFY( h->isEmpty() ); delete h; // white spaces and comment (from RFC 2822, Appendix A.5) h = new Date( 0, "Thu,\n 13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1969, 2, 13 ) ); QCOMPARE( h->dateTime().time(), QTime( 23, 32 ) ); QCOMPARE( h->dateTime().utcOffset(), -12600 ); QCOMPARE( h->as7BitString( false ), QByteArray( "Thu, 13 Feb 1969 23:32 -0330" ) ); delete h; // obsolete date format (from RFC 2822, Appendix A.6.2) h = new Date( 0, "21 Nov 97 09:55:06 GMT" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1997, 11, 21 ) ); QCOMPARE( h->dateTime().time(), QTime( 9, 55, 6 ) ); QCOMPARE( h->dateTime().utcOffset(), 0 ); delete h; // obsolete whitespaces and commnets (from RFC 2822, Appendix A.6.3) h = new Date( 0, "Fri, 21 Nov 1997 09(comment): 55 : 06 -0600" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->dateTime().date(), QDate( 1997, 11, 21 ) ); QCOMPARE( h->dateTime().time(), QTime( 9, 55, 6 ) ); QCOMPARE( h->dateTime().utcOffset(), -6 * 3600 ); delete h; } void HeaderTest::testLinesHeader() { Lines *h; // empty header h = new Lines(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); // set some content h->setNumberOfLines( 5 ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString(), QByteArray( "Lines: 5" ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse header with comment h = new Lines( 0, "(this is a comment) 10 (and yet another comment)" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->numberOfLines(), 10 ); delete h; } void HeaderTest::testNewsgroupsHeader() { Newsgroups *h; // empty header h = new Newsgroups(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); // set newsgroups QList groups; groups << "gmane.comp.kde.devel.core" << "gmane.comp.kde.devel.buildsystem"; h->setGroups( groups ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString(), QByteArray( "Newsgroups: gmane.comp.kde.devel.core,gmane.comp.kde.devel.buildsystem" ) ); // and clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a header h = new Newsgroups( 0, "gmane.comp.kde.devel.core,gmane.comp.kde.devel.buildsystem" ); groups = h->groups(); QCOMPARE( groups.count(), 2 ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.core") ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.buildsystem") ); delete h; // same again, this time with whitespaces and comments h = new Newsgroups(); h->from7BitString( "(comment) gmane.comp.kde.devel.core (second comment),\n gmane.comp.kde.devel.buildsystem (that all)" ); groups = h->groups(); QCOMPARE( groups.count(), 2 ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.core") ); QCOMPARE( groups.takeFirst(), QByteArray("gmane.comp.kde.devel.buildsystem") ); delete h; } void HeaderTest::testControlHeader() { Control *h; // empty header h = new Control(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); // set some content h->setCancel( "" ); QVERIFY( !h->isEmpty() ); QVERIFY( h->isCancel() ); QCOMPARE( h->as7BitString(), QByteArray( "Control: cancel " ) ); // clear again h->clear(); QVERIFY( h->isEmpty() ); delete h; // parse a control header h = new Control( 0, "cancel " ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->parameter(), QByteArray("") ); QVERIFY( h->isCancel() ); QCOMPARE( h->controlType(), QByteArray("cancel") ); delete h; } void HeaderTest::testReturnPath() { ReturnPath *h; h = new ReturnPath(); QVERIFY( h->isEmpty() ); QVERIFY( h->as7BitString().isEmpty() ); h->from7BitString( "" ); QVERIFY( !h->isEmpty() ); QCOMPARE( h->as7BitString( true ), QByteArray( "Return-Path: " ) ); delete h; } void HeaderTest::noAbstractHeaders() { From* h2 = new From(); delete h2; Sender* h3 = new Sender(); delete h3; To* h4 = new To(); delete h4; Cc* h5 = new Cc(); delete h5; Bcc* h6 = new Bcc(); delete h6; ReplyTo* h7 = new ReplyTo(); delete h7; Keywords* h8 = new Keywords(); delete h8; MIMEVersion* h9 = new MIMEVersion(); delete h9; MessageID* h10 = new MessageID(); delete h10; ContentID* h11 = new ContentID(); delete h11; Supersedes* h12 = new Supersedes(); delete h12; InReplyTo* h13 = new InReplyTo(); delete h13; References* h14 = new References(); delete h14; Generic* h15 = new Generic(); delete h15; Subject* h16 = new Subject(); delete h16; Organization* h17 = new Organization(); delete h17; ContentDescription* h18 = new ContentDescription(); delete h18; FollowUpTo* h22 = new FollowUpTo(); delete h22; UserAgent* h24 = new UserAgent(); delete h24; } +void HeaderTest::testClone() +{ + // Clone a simple header. + { + Subject *h = new Subject; + h->from7BitString( "our fortress is burning" ); + Subject *c = dynamic_cast( h->clone() ); + QVERIFY( c ); + h->from7BitString( "against the grain of the shattered sky" ); + QCOMPARE( c->as7BitString( false ), QByteArray( "our fortress is burning" ) ); + delete h; + delete c; + } + + // Clone a more complex header. + { + ContentType *h = new ContentType; + h->setMimeType( "some/x-type" ); + h->setCharset( "ancient-runes" ); + ContentType *c = dynamic_cast( h->clone() ); + QVERIFY( c ); + h->clear(); + QCOMPARE( c->mimeType(), QByteArray( "some/x-type" ) ); + QCOMPARE( c->charset(), QByteArray( "ancient-runes" ) ); + delete h; + delete c; + } + + // Clone an even more complex header. + { + const QString address1( "Me " ); + const QString address2( "You " ); + To *h = new To; + Types::Mailbox box; + box.fromUnicodeString( address1 ); + h->addAddress( box ); + box.fromUnicodeString( address2 ); + h->addAddress( box ); + To *c = dynamic_cast( h->clone() ); + QVERIFY( c ); + h->clear(); + QCOMPARE( c->prettyAddresses().count(), 2 ); + QCOMPARE( c->prettyAddresses().at( 0 ), address1 ); + QCOMPARE( c->prettyAddresses().at( 1 ), address2 ); + delete h; + delete c; + } + + // Special test for Generic since it has a special clone() implementation. + { + Generic *h = new Generic; + h->setType( "type" ); + h->from7BitString( "beforecataracts" ); + Generic *c = dynamic_cast( h->clone() ); + QVERIFY( c ); + h->setType( "newtype" ); + h->from7BitString( "aftercataracts" ); + QCOMPARE( c->type(), "type" ); + QCOMPARE( c->as7BitString( false ), QByteArray( "beforecataracts" ) ); + delete h; + delete c; + } +} + void HeaderTest::testInvalidButOkQEncoding() { // A stray '?' should not confuse the parser Subject subject; subject.from7BitString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC?" "?=" ); QCOMPARE( subject.as7BitString( false ), QByteArray( "Why? Why do some clients violate the RFC?" ) ); } void HeaderTest::testInvalidQEncoding_data() { QTest::addColumn("encodedWord"); // All examples below should not be treated as invalid encoded strings, since the '?=' is missing QTest::newRow("") << QString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC??" ); QTest::newRow("") << QString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC?" ); QTest::newRow("") << QString( "=?us-ascii?q?Why?_Why_do_some_clients_violate_the_RFC" ); } void HeaderTest::testInvalidQEncoding() { using namespace HeaderParsing; QFETCH( QString,encodedWord ); const char *data = encodedWord.toAscii().data(); const char *start = data + 1; const char *end = data + strlen( data ); QString result; QByteArray language; QByteArray usedCS; QVERIFY( !parseEncodedWord( start, end, result, language, usedCS ) ); } #include "headertest.moc" diff --git a/kmime/tests/headertest.h b/kmime/tests/headertest.h index 760fd6932..250102be3 100644 --- a/kmime/tests/headertest.h +++ b/kmime/tests/headertest.h @@ -1,56 +1,58 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_HEADERTEST_H #define KMIME_HEADERTEST_H #include class HeaderTest : public QObject { Q_OBJECT private Q_SLOTS: void testIdentHeader(); void testAddressListHeader(); void testMailboxListHeader(); void testSingleMailboxHeader(); void testMailCopiesToHeader(); void testParametrizedHeader(); void testContentDispositionHeader(); void testContentTypeHeader(); void testTokenHeader(); void testContentTransferEncoding(); void testPhraseListHeader(); void testDotAtomHeader(); void testDateHeader(); void testLinesHeader(); void testNewsgroupsHeader(); void testControlHeader(); void testReturnPath(); void testInvalidButOkQEncoding(); void testInvalidQEncoding(); void testInvalidQEncoding_data(); // makes sure we don't accidently have an abstract header class that's not // meant to be abstract void noAbstractHeaders(); + + void testClone(); }; #endif diff --git a/kmime/tests/kmime_content_test.cpp b/kmime/tests/kmime_content_test.cpp index ad0b9a317..a47544238 100644 --- a/kmime/tests/kmime_content_test.cpp +++ b/kmime/tests/kmime_content_test.cpp @@ -1,470 +1,635 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include - #include "kmime_content_test.h" + +#include #include #include #include #include using namespace KMime; QTEST_KDEMAIN( KMimeContentTest, NoGUI ) void KMimeContentTest::testGetHeaderInstance( ) { // stuff that looks trivial but breaks if you mess with virtual method signatures (see r534381) Headers::From *myfrom = new Headers::From(); QCOMPARE( myfrom->type(), "From" ); Headers::Base *mybase = myfrom; QCOMPARE( mybase->type(), "From" ); // getHeaderInstance() is protected, so we need to test it via KMime::Message Message *c = new Message(); Headers::From *f1 = c->from( true ); Headers::From *f2 = c->from( true ); QCOMPARE( f1, f2 ); delete c; } +void KMimeContentTest::testHeaderAddRemove() +{ + // Add a Content-Description header to a content. + Content *c = new Content; + QVERIFY( !c->contentDescription( false ) ); + c->contentDescription()->from7BitString( "description" ); + + // The content must now have the header. + QVERIFY( c->contentDescription( false ) ); + QCOMPARE( c->contentDescription()->as7BitString( false ), QByteArray( "description" ) ); + + // The content's head must also have the header. Save the head. + c->assemble(); + QByteArray head = c->head(); + + // Clear the content. It must now forget the cached header. + c->clear(); + QVERIFY( c->head().isEmpty() ); + QVERIFY( !c->contentDescription( false ) ); + + // Put the head back. It must now remember the header. + c->setHead( head ); + QVERIFY( !c->contentDescription( false ) ); + c->parse(); + QVERIFY( c->contentDescription( false ) ); + c->contentDescription()->from7BitString( "description" ); + + // Now remove the header explicitly. + bool ret = c->removeHeader( "Content-Description" ); + QVERIFY( ret ); + + // The content must have forgotten the header now. + QVERIFY( !c->contentDescription( false ) ); + + // And after assembly the header should stay gone. + c->assemble(); + QVERIFY( c->head().isEmpty() ); + QVERIFY( !c->contentDescription( false ) ); +} + +void KMimeContentTest::testHeaderAppendPrepend() +{ + Content *c = new Content; + QByteArray d1( "Resent-From: test1@example.com" ); + QByteArray d2( "Resent-From: test2@example.com" ); + Headers::Generic *h1 = new Headers::Generic( "Resent-From", 0, "test1@example.com" ); + Headers::Generic *h2 = new Headers::Generic( "Resent-From", 0, "test2@example.com" ); + c->appendHeader( h1 ); + c->appendHeader( h2 ); + c->assemble(); + QByteArray head = d1 + '\n' + d2 + '\n'; + QCOMPARE( c->head(), head ); + + QByteArray d3( "Resent-From: test3@example.com" ); + Headers::Generic *h3 = new Headers::Generic( "Resent-From", 0, "test3@example.com" ); + c->prependHeader( h3 ); + c->assemble(); + head.prepend( d3 + '\n' ); + QCOMPARE( c->head(), head ); +} + +void KMimeContentTest::testImplicitMultipartGeneration() +{ + Content *c1 = new Content(); + c1->contentType()->from7BitString( "text/plain" ); + c1->setBody( "textpart" ); + + Content *c2 = new Content(); + c2->contentType()->from7BitString( "text/html" ); + c2->setBody( "htmlpart" ); + + c1->addContent( c2 ); + + // c1 implicitly converted into a multipart/mixed node. + QVERIFY( c1->contentType( false ) ); + QCOMPARE( c1->contentType()->mimeType(), QByteArray("multipart/mixed") ); + QVERIFY( c1->body().isEmpty() ); + + QCOMPARE( c1->contents().count(), 2 ); + Content *c = c1->contents().at( 0 ); // Former c1. + QVERIFY( c->contentType( false ) ); + QCOMPARE( c->contentType()->mimeType(), QByteArray("text/plain") ); + QCOMPARE( c->body(), QByteArray("textpart") ); + + QCOMPARE( c1->contents().at( 1 ), c2 ); + + // Now remove c2. c1 should be converted back to a text/plain content. + c1->removeContent( c2, false ); + QVERIFY( c1->contents().isEmpty() ); + QVERIFY( c1->contentType( false ) ); + QCOMPARE( c1->contentType()->mimeType(), QByteArray( "text/plain" ) ); + QCOMPARE( c1->body(), QByteArray( "textpart" ) ); + + // c2 should not have been touched. + QVERIFY( c2->contents().isEmpty() ); + QVERIFY( c2->contentType( false ) ); + QCOMPARE( c2->contentType()->mimeType(), QByteArray( "text/html" ) ); + QCOMPARE( c2->body(), QByteArray( "htmlpart" ) ); + + // Clean up. + delete c1; + delete c2; +} + +void KMimeContentTest::testExplicitMultipartGeneration() +{ + Content *c1 = new Content(); + c1->contentType()->from7BitString( "multipart/mixed" ); + + Content *c2 = new Content(); + c2->contentType()->from7BitString( "text/plain" ); + c2->setBody( "textpart" ); + + Content *c3 = new Content(); + c3->contentType()->from7BitString( "text/html" ); + c3->setBody( "htmlpart" ); + + c1->addContent( c2 ); + c1->addContent( c3 ); + + // c1 should not have been changed. + QCOMPARE( c1->contentType()->mimeType(), QByteArray("multipart/mixed") ); + QVERIFY( c1->body().isEmpty() ); + + QCOMPARE( c1->contents().count(), 2 ); + QCOMPARE( c1->contents().at( 0 ), c2 ); + QCOMPARE( c1->contents().at( 1 ), c3 ); + + // Removing c3 should turn c1 into a single-part content containing the data of c2. + c1->removeContent( c3, false ); + QCOMPARE( c1->contentType()->mimeType(), QByteArray( "text/plain" ) ); + QCOMPARE( c1->contents().count(), 0 ); + QCOMPARE( c1->body(), QByteArray( "textpart" ) ); + + // Clean up. + delete c1; + // c2 was deleted when c1 turned itself single-part. + delete c3; +} + void KMimeContentTest::testSetContent() { Content *c = new Content(); QVERIFY( !c->hasContent() ); // head and body present c->setContent( "head1\nhead2\n\nbody1\n\nbody2\n" ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); QList list; list << "head1" << "head2" << "" << "body1" << "" << "body2"; c->setContent( list ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); // ### the final \n is questionable // empty content c->setContent( QByteArray() ); QVERIFY( !c->hasContent() ); QVERIFY( c->head().isEmpty() ); QVERIFY( c->body().isEmpty() ); // empty head c->setContent( "\nbody1\n\nbody2\n" ); QVERIFY( c->hasContent() ); QVERIFY( c->head().isEmpty() ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); list.clear(); list << "" << "body1" << "" << "body2"; c->setContent( list ); QVERIFY( c->hasContent() ); QVERIFY( c->head().isEmpty() ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); // empty body c->setContent( "head1\nhead2\n\n" ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QVERIFY( c->body().isEmpty() ); list.clear(); list << "head1" << "head2" << ""; c->setContent( list ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QVERIFY( c->body().isEmpty() ); } +void KMimeContentTest::testEncodedContent() +{ + // Example taken from RFC 2046, section 5.1.1. + // Removed "preamble" and "epilogue", which KMime loses. + QByteArray data = + "From: Nathaniel Borenstein \n" + "To: Ned Freed \n" + "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" + "Subject: Sample message\n" + "MIME-Version: 1.0\n" + "Content-type: multipart/mixed; boundary=\"simple boundary\"\n" + "\n" + "\n" + "--simple boundary\n" + "\n" + "This is implicitly typed plain US-ASCII text.\n" + "It does NOT end with a linebreak.\n" + "--simple boundary\n" + "Content-type: text/plain; charset=us-ascii\n" + "\n" + "This is explicitly typed plain US-ASCII text.\n" + "It DOES end with a linebreak.\n" + "\n" + "--simple boundary--\n"; + + Message *msg = new Message; + msg->setContent( data ); + msg->parse(); + + // Test that multiple calls do not corrupt anything. + QByteArray encc = msg->encodedContent(); + //kDebug() << "original data" << data; + //kDebug() << "encodedContent" << encc; + QCOMPARE( msg->encodedContent(), data ); + QCOMPARE( msg->encodedContent(), data ); + QCOMPARE( msg->encodedContent(), data ); +} + void KMimeContentTest::testMultipleHeaderExtraction() { QByteArray data = "From: Nathaniel Borenstein \n" "To: Ned Freed \n" "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" "Subject: Sample message\n" "Received: from ktown.kde.org ([192.168.100.1])\n" "Received: from dev1.kde.org ([192.168.100.2])\n" "\t by ktown.kde.org ([192.168.100.1])\n" "Received: from dev2.kde.org ([192.168.100.3])\n" " by ktown.kde.org ([192.168.100.1])\n"; Message *msg = new Message(); msg->setContent( data ); // FAILS identically to KMimeContentTest::testMultipartMixed // QCOMPARE( msg->encodedContent(), data ); msg->parse(); QList result = msg->headersByType( "Received" ); QCOMPARE( result.count(), 3 ); QCOMPARE( result[0]->asUnicodeString(), QString("from ktown.kde.org ([192.168.100.1])") ); QCOMPARE( result[1]->asUnicodeString(), QString("from dev1.kde.org ([192.168.100.2]) by ktown.kde.org ([192.168.100.1])") ); QCOMPARE( result[2]->asUnicodeString(), QString("from dev2.kde.org ([192.168.100.3]) by ktown.kde.org ([192.168.100.1])") ); } void KMimeContentTest::testMultipartMixed() { // example taken from RFC 2046, section 5.1.1. QByteArray data = "From: Nathaniel Borenstein \n" "To: Ned Freed \n" "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" "Subject: Sample message\n" "MIME-Version: 1.0\n" "Content-type: multipart/mixed; boundary=\"simple boundary\"\n" "\n" "This is the preamble. It is to be ignored, though it\n" "is a handy place for composition agents to include an\n" "explanatory note to non-MIME conformant readers.\n" "\n" "--simple boundary\n" "\n" "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak.\n" "--simple boundary\n" "Content-type: text/plain; charset=us-ascii\n" "\n" "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n" "\n" "--simple boundary--\n" "\n" "This is the epilogue. It is also to be ignored.\n"; QByteArray part1 = "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak."; QByteArray part2 = "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n"; - // slightly diffrent from original data + // What we expect KMime to assemble the above data into. QByteArray assembled = "From: Nathaniel Borenstein \n" "To: Ned Freed \n" - "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" + "Date: Sun, 21 Mar 1993 23:56:48 -0800\n" "Subject: Sample message\n" "MIME-Version: 1.0\n" - "Content-type: multipart/mixed; boundary=\"simple boundary\"\n" + "Content-Type: multipart/mixed; boundary=\"simple boundary\"\n" "\n" "\n" "--simple boundary\n" "\n" "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak.\n" "--simple boundary\n" - "Content-type: text/plain; charset=us-ascii\n" + "Content-Type: text/plain; charset=\"us-ascii\"\n" "\n" "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n" "\n" "--simple boundary--\n"; // test parsing Message *msg = new Message(); msg->setContent( data ); QCOMPARE( msg->encodedContent(), data ); msg->parse(); QVERIFY( msg->contentType()->isMultipart() ); Content::List list = msg->contents(); QCOMPARE( list.count(), 2 ); Content *c = list.takeFirst(); QCOMPARE( c->body(), part1 ); c = list.takeFirst(); QCOMPARE( c->body(), part2 ); // assemble again msg->assemble(); + //kDebug() << "expected assembled content" << assembled; + //kDebug() << "actual new encoded content" << msg->encodedContent(); QCOMPARE( msg->encodedContent(), assembled ); delete msg; // assembling from scratch - // slightly diffrent from original data - QByteArray assembled2 = - "From: Nathaniel Borenstein \n" - "Subject: Sample message\n" - "To: Ned Freed \n" - "Date: Sun, 21 Mar 1993 23:56:48 -0800\n" - "MIME-Version: 1.0\n" - "Content-Type: multipart/mixed; boundary=\"simple boundary\"\n" - "\n" - "\n" - "--simple boundary\n" - "\n" - "This is implicitly typed plain US-ASCII text.\n" - "It does NOT end with a linebreak.\n" - "--simple boundary\n" - "Content-Type: text/plain; charset=\"us-ascii\"\n" - "\n" - "This is explicitly typed plain US-ASCII text.\n" - "It DOES end with a linebreak.\n" - "\n" - "--simple boundary--\n"; - + // (The headers have to be in the same order, as we compare with the above assembled.) msg = new Message(); msg->from()->from7BitString( "Nathaniel Borenstein " ); msg->to()->from7BitString( "Ned Freed " ); - msg->subject()->from7BitString( "Sample message" ); msg->date()->from7BitString( "Sun, 21 Mar 1993 23:56:48 -0800 (PST)" ); + msg->subject()->from7BitString( "Sample message" ); + // HACK to make MIME-Version appear before Content-Type, as in the expected message. + msg->setHeader( new Headers::MIMEVersion( msg, "1.234" ) ); msg->setBody( part1 ); c = new Content(); c->setBody( part2 ); c->contentType()->setMimeType( "text/plain" ); c->contentType()->setCharset( "us-ascii" ); msg->addContent( c ); msg->contentType()->setBoundary( "simple boundary" ); list = msg->contents(); QCOMPARE( list.count(), 2 ); c = list.takeFirst(); QCOMPARE( c->body(), part1 ); c = list.takeFirst(); QCOMPARE( c->body(), part2 ); msg->assemble(); - QCOMPARE( msg->encodedContent(), assembled2 ); -} - -void KMimeContentTest::testImplicitMultipartGeneration() -{ - Content *c1 = new Content(); - c1->contentType()->from7BitString( "text/plain" ); - c1->setBody( "textpart" ); - - Content *c2 = new Content(); - c2->contentType()->from7BitString( "text/html" ); - c2->setBody( "htmlpart" ); - - c1->addContent( c2 ); - - // c1 implicitly converted into a multipart/mixed node - QCOMPARE( c1->contentType()->mimeType(), QByteArray("multipart/mixed") ); - QVERIFY( c1->body().isEmpty() ); - - Content *c = c1->contents().at( 0 ); // former c1 - QCOMPARE( c->contentType()->mimeType(), QByteArray("text/plain") ); - QCOMPARE( c->body(), QByteArray("textpart") ); - - QCOMPARE( c1->contents().at( 1 ), c2 ); -} - -void KMimeContentTest::testExplicitMultipartGeneration() -{ - Content *c1 = new Content(); - c1->contentType()->from7BitString( "multipart/mixed" ); - - Content *c2 = new Content(); - c2->contentType()->from7BitString( "text/plain" ); - c2->setBody( "textpart" ); - - Content *c3 = new Content(); - c3->contentType()->from7BitString( "text/html" ); - c3->setBody( "htmlpart" ); - - c1->addContent( c2 ); - c1->addContent( c3 ); - - // c1 should not be changed - QCOMPARE( c1->contentType()->mimeType(), QByteArray("multipart/mixed") ); - QVERIFY( c1->body().isEmpty() ); - - QCOMPARE( c1->contents().at( 0 ), c2 ); - QCOMPARE( c1->contents().at( 1 ), c3 ); + QByteArray encc = msg->encodedContent(); + //kDebug() << "expected assembled content" << assembled; + //kDebug() << "actual encoded content" << encc; + QCOMPARE( msg->encodedContent(), assembled ); } void KMimeContentTest::testParsingUuencoded() { const QByteArray body = "This is a test message that should appears as a text/plain part\n" "once this message is parsed and convert to a MIME tree.\n" "\n" "\n"; const QString imageName = "Name of the encoded file (oxygen 22x22 kde.png)"; const QByteArray uuencodedMsg = "Path: news.example.net!not-for-mail\n" "From: Coin coin \n" "Newsgroups: test.kmime.uuencoded\n" "Subject: Kmime test\n" "Date: Thu, 14 Apr 2005 20:12:47 -0700\n" "Message-ID: \n" "X-Newsreader: Forte Agent 2.0/32.640\n" "Lines: 1283\n" "Organization: Ament\n" "Xref: news.example.net test.kmime.uuencoded:4584\n" "\n" "This is a test message that should appears as a text/plain part\n" "once this message is parsed and convert to a MIME tree.\n" "\n" "begin 644 Name of the encoded file (oxygen 22x22 kde.png)\n" "MB5!.1PT*&@H````-24A$4@```!8````6\"`8```#$M&P[````!'-\"250(\"`@(\n" "M?`ADB`````EP2%ES```#=@```W8!?=6\"S````!ET15AT4V]F='=A^U1FZM2*X2V035`Q5.9UM3S:>&Z__]?36N1],G.QRQ[_QS:6C!YX\\R)\n" "M;C]FS/&)EU&=IH<.PEQ\"\",LUJMK*2K9VT9UVZ.V9D62)H.TK#RE4:;\n" "M1$1..L\"H&WSY\\)$#K*X-(%0,!ST6!EV&W91>&B-B^=>;.Z@*\"WL'#`8[NIG@$<=8(RXI2@5)MF,8;^'F!B5&$]$HX[9=L')\n" "MCW>)G.'I,W/Z>Q;()QF??K_B;,*Y\\S-^,XGPE&\"3/F`,N\\08*:L\"%8=U\"6H2\n" "M@D3X8&F\"H6@@31+B.*'T4%3*`\\O;1,[AG$.S\"5_\\\n" "MQ\"K.6OYVJ>*5&QT\"!I4(%W>_VZ$,@::%5H6Z5L1:&766K%GJ6R/625TKK4+E\n" "M`[,XY==_N$'9P.E?O,&6ZR`BB`AK*PD@&($B+\\CKFKRNJ6I/6;6(M83E-6O:\n" "MN&>-L\\Q+3]6V%%7#=&/,P;TISYZ]PL4JIJ@;0`#AF>?'/F]QRWWL7N;M7LCW-:)J*-C2`T*>/$5LJ*$$]=963.!\"4M@UL9H:FAK8%\n" "M5064IA5^=/8FEZ[EW/+MS]])K\\SQ=8GW-2)*1H8QA09V^:9\"M>6A9R[RR=.O\n" "M<>'-.7>N6-1;0F,((:\"J&&)<.N+TLY?)2\\^@8WC\\4X?0VA/J$@T!J0G\"L4<&\n" "M@[7C-Q975U,;)0QZ(U96]O/@>X0'/KC,X[_3YC%FVPW2Z1613\n" "M]HSV<77]E3\\\"K:@JLO]#*]W%X\\_W]]YS?[>_)XU=A#,6:P0DH-KB38;7G,CV\n" "MB;1+)%WBN$MD$U2$?+[57+OTTJLW7S_SI6I\\X8*H*K>(B`-&P'Y``.'_H[QE\n" ";$[@.>-WU'U(#IV=EWM`H`````$E%3D2N0F\"\"\n" "`\n" "end\n" "\n"; Message *msg = new Message(); msg->setContent( uuencodedMsg ); msg->parse(); Content::List contents = msg->contents(); // text + image QCOMPARE( contents.size(), 2 ); Content *c = 0; // Check the first text part c = contents.at( 0 ); QVERIFY( c->contentType()->isPlainText() ); QCOMPARE( c->body(), body ); // Check the image part c = contents.at( 1 ); QVERIFY( !c->contentType()->isText() ); QCOMPARE( c->contentType()->name(), imageName ); delete msg; } void KMimeContentTest::testParent() { Content *c1 = new Content(); c1->contentType()->from7BitString( "multipart/mixed" ); Content *c2 = new Content(); c2->contentType()->from7BitString( "text/plain" ); c2->setBody( "textpart" ); Content *c3 = new Content(); c3->contentType()->from7BitString( "text/html" ); c3->setBody( "htmlpart" ); Content *c4 = new Content(); c4->contentType()->from7BitString( "text/html" ); c4->setBody( "htmlpart2" ); Content *c5 = new Content(); c5->contentType()->from7BitString( "multipart/mixed" ); -//c2 doesn't have a parent yet + //c2 doesn't have a parent yet QCOMPARE(c2->parent(), (Content*)(0L)); c1->addContent( c2 ); c1->addContent( c3 ); c1->addContent( c4 ); // c1 is the parent of those QCOMPARE( c2->parent(), c1 ); QCOMPARE( c3->parent(), c1 ); //test removal c1->removeContent(c2, false); QCOMPARE(c2->parent(), (Content*)(0L)); QCOMPARE(c1->contents().at( 0 ), c3 ); //check if the content is moved correctly to another parent c5->addContent( c4 ); QCOMPARE(c4->parent(), c5); QCOMPARE(c1->contents().count(), 0); //yes, it should be 0 QCOMPARE(c5->contents().at( 0 ), c4); // example taken from RFC 2046, section 5.1.1. QByteArray data = "From: Nathaniel Borenstein \n" "To: Ned Freed \n" "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" "Subject: Sample message\n" "MIME-Version: 1.0\n" "Content-type: multipart/mixed; boundary=\"simple boundary\"\n" "\n" "This is the preamble. It is to be ignored, though it\n" "is a handy place for composition agents to include an\n" "explanatory note to non-MIME conformant readers.\n" "\n" "--simple boundary\n" "\n" "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak.\n" "--simple boundary\n" "Content-type: text/plain; charset=us-ascii\n" "\n" "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n" "\n" "--simple boundary--\n" "\n" "This is the epilogue. It is also to be ignored.\n"; // test parsing Message *msg = new Message(); msg->setContent( data ); msg->parse(); QCOMPARE( msg->parent(), (Content*)(0L)); QCOMPARE( msg->contents().at( 0 )->parent(), msg); QCOMPARE( msg->contents().at( 1 )->parent(), msg); delete msg; } +void KMimeContentTest::testFreezing() +{ + // Example taken from RFC 2046, section 5.1.1. + QByteArray data = + "From: Nathaniel Borenstein \n" + "To: Ned Freed \n" + "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" + "Subject: Sample message\n" + "MIME-Version: 1.0\n" + "Content-type: multipart/mixed; boundary=\"simple boundary\"\n" + "\n" + "This is the preamble. It is to be ignored, though it\n" + "is a handy place for composition agents to include an\n" + "explanatory note to non-MIME conformant readers.\n" + "\n" + "--simple boundary\n" + "\n" + "This is implicitly typed plain US-ASCII text.\n" + "It does NOT end with a linebreak.\n" + "--simple boundary\n" + "Content-type: text/plain; charset=us-ascii\n" + "\n" + "This is explicitly typed plain US-ASCII text.\n" + "It DOES end with a linebreak.\n" + "\n" + "--simple boundary--\n" + "\n" + "This is the epilogue. It is also to be ignored.\n"; + + Message *msg = new Message; + msg->setContent( data ); + msg->setFrozen( true ); + + // The data should be untouched before parsing. + //kDebug() << "original data" << data; + //kDebug() << "data from message" << msg->encodedContent(); + QCOMPARE( msg->encodedContent(), data ); + + // The data should remain untouched after parsing. + msg->parse(); + QVERIFY( msg->contentType()->isMultipart() ); + QCOMPARE( msg->contents().count(), 2 ); + QCOMPARE( msg->encodedContent(), data ); + + // Calling assemble() should not alter the data. + msg->assemble(); + QCOMPARE( msg->encodedContent(), data ); +} #include "kmime_content_test.moc" diff --git a/kmime/tests/kmime_content_test.h b/kmime/tests/kmime_content_test.h index 148a4e499..03043c46c 100644 --- a/kmime/tests/kmime_content_test.h +++ b/kmime/tests/kmime_content_test.h @@ -1,43 +1,48 @@ /* Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_CONTENT_TEST_H #define KMIME_CONTENT_TEST_H #include class KMimeContentTest : public QObject { Q_OBJECT private Q_SLOTS: void testGetHeaderInstance(); - void testSetContent(); - void testMultipartMixed(); + void testHeaderAddRemove(); + void testHeaderAppendPrepend(); void testImplicitMultipartGeneration(); void testExplicitMultipartGeneration(); + void testSetContent(); + void testEncodedContent(); + void testMultipartMixed(); void testMultipleHeaderExtraction(); /** Tests that a message with uuencoded content is parsed correctly and if a corresponding MIME structure is created. */ void testParsingUuencoded(); + // TODO: grab samples from http://www.yenc.org/develop.htm and make a Yenc test void testParent(); + void testFreezing(); }; #endif diff --git a/kmime/tests/kmime_headerfactorytest.cpp b/kmime/tests/kmime_headerfactorytest.cpp new file mode 100644 index 000000000..8780dc35f --- /dev/null +++ b/kmime/tests/kmime_headerfactorytest.cpp @@ -0,0 +1,161 @@ +/* + Copyright (c) 2009 Constantin Berzan + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include "kmime_headerfactorytest.h" + +//#include + +#include +#include + +#include +#include + +using namespace KMime; +using namespace KMime::Headers; +//using namespace KMime::Headers::Generics; + +QTEST_KDEMAIN( HeaderFactoryTest, NoGUI ) + +// This cannot be defined in a function, because the template code +// in HeaderFactory::registerHeader() needs it. +class MyXHeader : public ContentType +{ + public: + const char *type() const + { + return "X-My-Content-Type"; + } + + virtual Base *clone() const + { + MyXHeader *ret = new MyXHeader; + ret->from7BitString( as7BitString( false ) ); + return ret; + } +}; + +template +bool isHeaderRegistered() +{ + T dummy; + Base *h = HeaderFactory::self()->createHeader( dummy.type() ); + if( h ) { + delete h; + return true; + } + return false; +} + +void HeaderFactoryTest::testBuiltInHeaders() +{ + // Abstract headers have pure virtual methods. + // Generic headers have an empty type(). + // All other built-in headers are supposed to be registered. + + //QVERIFY( isHeaderRegistered() ); // Abstract. + //QVERIFY( isHeaderRegistered() ); // Abstract. + //QVERIFY( isHeaderRegistered() ); // Abstract. + //QVERIFY( isHeaderRegistered
() ); // Abstract. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + //QVERIFY( isHeaderRegistered() ); // Generic. + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + //QVERIFY( isHeaderRegistered() ); // Changeable type(). + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); + QVERIFY( isHeaderRegistered() ); +} + +void HeaderFactoryTest::testCustomHeaders() +{ + MyXHeader dummy; + + // Before registration: + { + Base *bh = HeaderFactory::self()->createHeader( dummy.type() ); + QVERIFY( bh == 0 ); + } + + // Register: + { + bool ret = HeaderFactory::self()->registerHeader(); + QVERIFY( ret == true ); + } + + // After registration: + { + Base *bh = HeaderFactory::self()->createHeader( dummy.type() ); + MyXHeader *h = dynamic_cast( bh ); + QVERIFY( h ); + } + + // Should be case-insensitive. + { + Base *bh = HeaderFactory::self()->createHeader( "x-mY-CoNtEnT-tYpE" ); + MyXHeader *h = dynamic_cast( bh ); + QVERIFY( h ); + } +} + +void HeaderFactoryTest::testErrors() +{ + // Cannot register a generic (empty-type()) header: + { + bool ret = HeaderFactory::self()->registerHeader(); + QVERIFY( ret == false ); + } + + // Repeated registration should fail. + MyXHeader dummy; + bool ret = HeaderFactory::self()->registerHeader(); + QVERIFY( ret == false ); +} + +#include "kmime_headerfactorytest.moc" diff --git a/kmime/kmime_message_p.h b/kmime/tests/kmime_headerfactorytest.h similarity index 63% copy from kmime/kmime_message_p.h copy to kmime/tests/kmime_headerfactorytest.h index 668f3de89..362a0d51f 100644 --- a/kmime/kmime_message_p.h +++ b/kmime/tests/kmime_headerfactorytest.h @@ -1,48 +1,36 @@ /* - Copyright (c) 2007 Volker Krause + Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef KMIME_MESSAGE_P_H -#define KMIME_MESSAGE_P_H +#ifndef KMIME_HEADERFACTORYTEST_H +#define KMIME_HEADERFACTORYTEST_H -#include "kmime_content_p.h" +#include -// @cond PRIVATE - -namespace KMime { - -class MessagePrivate : public ContentPrivate +class HeaderFactoryTest : public QObject { - public: - MessagePrivate( Message *q ) : ContentPrivate( q ) - { - subject.setParent( q ); - date.setParent( q ); - } - - KMime::Headers::Subject subject; - KMime::Headers::Date date; + Q_OBJECT + private Q_SLOTS: + void testBuiltInHeaders(); + void testCustomHeaders(); + void testErrors(); - Q_DECLARE_PUBLIC(Message) + // TODO test how unregistered headers are handled by Content. }; -} - -// @endcond - #endif