diff --git a/kmime/kmime_charfreq.cpp b/kmime/kmime_charfreq.cpp index def2174b1..6880a2417 100644 --- a/kmime/kmime_charfreq.cpp +++ b/kmime/kmime_charfreq.cpp @@ -1,252 +1,252 @@ /* kmime_charfreq.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. */ /** @file This file is part of the API for handling MIME data and defines the CharFreq class. @brief Defines the CharFreq class. @authors Marc Mutz \ */ #include "kmime_charfreq.h" using namespace KMime; /** * Private class that helps to provide binary compatibility between releases. * @internal */ //@cond PRIVATE //class KMime::CharFreq::Private //{ // public: //}; //@endcond CharFreq::CharFreq( const QByteArray &buf ) : mNUL( 0 ), mCTL( 0 ), mCR( 0 ), mLF( 0 ), mCRLF( 0 ), mPrintable( 0 ), mEightBit( 0 ), mTotal( 0 ), mLineMin( 0xffffffff ), mLineMax( 0 ), mTrailingWS( false ), mLeadingFrom( false ) { if ( !buf.isEmpty() ) { count( buf.data(), buf.size() ); } } CharFreq::CharFreq( const char *buf, size_t len ) : mNUL( 0 ), mCTL( 0 ), mCR( 0 ), mLF( 0 ), mCRLF( 0 ), mPrintable( 0 ), mEightBit( 0 ), mTotal( 0 ), mLineMin( 0xffffffff ), mLineMax( 0 ), mTrailingWS( false ), mLeadingFrom( false ) { if ( buf && len > 0 ) { count( buf, len ); } } //@cond PRIVATE static inline bool isWS( char ch ) { return ( ch == '\t' || ch == ' ' ); } //@endcond void CharFreq::count( const char *it, size_t len ) { const char *end = it + len; uint currentLineLength = 0; // initialize the prevChar with LF so that From_ detection works w/o // special-casing: char prevChar = '\n'; char prevPrevChar = 0; for ( ; it != end ; ++it ) { ++currentLineLength; switch ( *it ) { case '\0': ++mNUL; break; case '\r': ++mCR; break; case '\n': ++mLF; if ( prevChar == '\r' ) { --currentLineLength; ++mCRLF; } if ( currentLineLength >= mLineMax ) { mLineMax = currentLineLength-1; } if ( currentLineLength <= mLineMin ) { mLineMin = currentLineLength-1; } if ( !mTrailingWS ) { if ( isWS( prevChar ) || ( prevChar == '\r' && isWS( prevPrevChar ) ) ) { mTrailingWS = true; } } currentLineLength = 0; break; case 'F': // check for lines starting with From_ if not found already: if ( !mLeadingFrom ) { if ( prevChar == '\n' && end - it >= 5 && !qstrncmp( "From ", it, 5 ) ) { mLeadingFrom = true; } } ++mPrintable; break; default: { uchar c = *it; if ( c == '\t' || ( c >= ' ' && c <= '~' ) ) { ++mPrintable; } else if ( c == 127 || c < ' ' ) { ++mCTL; } else { ++mEightBit; } } } prevPrevChar = prevChar; prevChar = *it; } // consider the length of the last line if ( currentLineLength >= mLineMax ) { mLineMax = currentLineLength; } if ( currentLineLength <= mLineMin ) { mLineMin = currentLineLength; } // check whether the last character is tab or space if ( isWS( prevChar ) ) { mTrailingWS = true; } mTotal = len; } bool CharFreq::isEightBitData() const { return type() == EightBitData; } bool CharFreq::isEightBitText() const { return type() == EightBitText; } bool CharFreq::isSevenBitData() const { return type() == SevenBitData; } bool CharFreq::isSevenBitText() const { return type() == SevenBitText; } bool CharFreq::hasTrailingWhitespace() const { return mTrailingWS; } bool CharFreq::hasLeadingFrom() const { return mLeadingFrom; } CharFreq::Type CharFreq::type() const { #if 0 qDebug( "Total: %d; NUL: %d; CTL: %d;\n" "CR: %d; LF: %d; CRLF: %d;\n" "lineMin: %d; lineMax: %d;\n" "printable: %d; eightBit: %d;\n" "trailing whitespace: %s;\n" "leading 'From ': %s;\n", total, NUL, CTL, CR, LF, CRLF, lineMin, lineMax, printable, eightBit, mTrailingWS ? "yes" : "no" , mLeadingFrom ? "yes" : "no" ); #endif if ( mNUL ) { // must be binary return Binary; } // doesn't contain NUL's: if ( mEightBit ) { if ( mLineMax > 988 ) { return EightBitData; // not allowed in 8bit } - if ( mCR != mCRLF || controlCodesRatio() > 0.2 ) { + if ( mLF != mCRLF || mCR != mCRLF || controlCodesRatio() > 0.2 ) { return EightBitData; } return EightBitText; } // doesn't contain NUL's, nor 8bit chars: if ( mLineMax > 988 ) { return SevenBitData; } - if ( mCR != mCRLF || controlCodesRatio() > 0.2 ) { + if ( mLF != mCRLF || mCR != mCRLF || controlCodesRatio() > 0.2 ) { return SevenBitData; } // no NUL, no 8bit chars, no excessive CTLs and no lines > 998 chars: return SevenBitText; } float CharFreq::printableRatio() const { if ( mTotal ) { return float(mPrintable) / float(mTotal); } else { return 0; } } float CharFreq::controlCodesRatio() const { if ( mTotal ) { return float(mCTL) / float(mTotal); } else { return 0; } } diff --git a/kmime/kmime_charfreq.h b/kmime/kmime_charfreq.h index af2fc0b35..d5fde8c63 100644 --- a/kmime/kmime_charfreq.h +++ b/kmime/kmime_charfreq.h @@ -1,179 +1,184 @@ /* -*- c++ -*- kmime_charfreq.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. */ /** @file This file is part of the API for handling @ref MIME data and defines the CharFreq class. @brief Defines the CharFreq class. @authors Marc Mutz \ @glossary @anchor Eight-Bit @anchor eight-bit @b 8-bit: - Data that contains bytes with at least one value greater than 127. + Data that contains bytes with at least one value greater than 127, or at + least one NUL byte. @glossary @anchor Eight-Bit-Binary @anchor eight-bit-binary @b 8-bit-binary: - Eight-bit data that contains a high percentage of non-ascii values. + Eight-bit data that contains a high percentage of non-ascii values, + or lines longer than 998 characters, or stray CRs or LFs, or NULs. @glossary @anchor Eight-Bit-Text @anchor eight-bit-text @b 8-bit-text: - Eight-bit data that contains a high percentage of non-ascii values. + Eight-bit data that contains a high percentage of ascii values, + no lines longer than 998 characters, no stray CRs or LFs, and no NULs. @glossary @anchor Seven-Bit @anchor seven-bit @b 7-Bit: - Data that contains bytes with all values less than 128. + Data that contains bytes with all values less than 128, and no NULs. @glossary @anchor Seven-Bit-Binary @anchor seven-bit-binary @b 7-bit-binary: - Seven-bit data that contains a high percentage of non-ascii values. + Seven-bit data that contains a high percentage of non-ascii values, + or lines longer than 998 characters, or stray CRs or LFs. @glossary @anchor Seven-Bit-Text @anchor seven-bit-text @b 7-bit-text: - Seven-bit data that contains a high percentage of ascii values. + Seven-bit data that contains a high percentage of ascii values, + no lines longer than 998 characters, no stray CRs or LFs. */ #ifndef __KMIME_CHARFREQ_H__ #define __KMIME_CHARFREQ_H__ #include #include "kmime_export.h" #undef None namespace KMime { /** @brief A class for performing basic data typing using frequency count heuristics. This class performs character frequency counts on the provided data which are used in heuristics to determine a basic data type. The data types are: - @ref Eight-Bit-Binary - @ref Eight-Bit-Text - @ref Seven-Bit-Binary - @ref Seven-Bit-Text */ class KMIME_EXPORT CharFreq { public: /** Constructs a Character Frequency instance for a buffer @p buf of QByteArray data. @param buf is a QByteArray containing the data. */ explicit CharFreq( const QByteArray &buf ); /** Constructs a Character Frequency instance for a buffer @p buf of chars of length @p len. @param buf is a pointer to a character string containing the data. @param len is the length of @p buf, in characters. */ CharFreq( const char *buf, size_t len ); /** The different types of data. */ enum Type { None = 0, /**< Unknown */ EightBitData, /**< 8bit binary */ Binary = EightBitData, /**< 8bit binary */ SevenBitData, /**< 7bit binary */ EightBitText, /**< 8bit text */ SevenBitText /**< 7bit text */ }; /** Returns the data #Type as derived from the class heuristics. */ Type type() const; /** Returns true if the data #Type is EightBitData; false otherwise. */ bool isEightBitData() const; /** Returns true if the data #Type is EightBitText; false otherwise. */ bool isEightBitText() const; /** Returns true if the data #Type is SevenBitData; false otherwise. */ bool isSevenBitData() const; /** Returns true if the data #Type is SevenBitText; false otherwise. */ bool isSevenBitText() const; /** Returns true if the data contains trailing whitespace. i.e., if any line ends with space (' ') or tab ('\\t'). */ bool hasTrailingWhitespace() const; /** Returns true if the data contains a line that starts with "From ". */ bool hasLeadingFrom() const; /** Returns the percentage of printable characters in the data. The result is undefined if the number of data characters is zero. */ float printableRatio() const; /** Returns the percentage of control code characters (CTLs) in the data. The result is undefined if the number of data characters is zero. */ float controlCodesRatio() const; private: //@cond PRIVATE uint mNUL; // count of NUL chars uint mCTL; // count of CTLs (incl. DEL, excl. CR, LF, HT) uint mCR; // count of CR chars uint mLF; // count of LF chars uint mCRLF; // count of LFs, preceded by CRs uint mPrintable; // count of printable US-ASCII chars (SPC..~) uint mEightBit; // count of other latin1 chars (those with 8th bit set) uint mTotal; // count of all chars uint mLineMin; // minimum line length uint mLineMax; // maximum line length bool mTrailingWS; // does the buffer contain trailing whitespace? bool mLeadingFrom; // does the buffer contain lines starting with "From "? //@endcond /** Performs the character frequency counts on the data. @param buf is a pointer to a character string containing the data. @param len is the length of @p buf, in characters. */ void count( const char *buf, size_t len ); }; } // namespace KMime #endif /* __KMIME_CHARFREQ_H__ */ diff --git a/kmime/kmime_content.cpp b/kmime/kmime_content.cpp index 9bc973863..051b8b36a 100644 --- a/kmime/kmime_content.cpp +++ b/kmime/kmime_content.cpp @@ -1,1200 +1,1209 @@ /* 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 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_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() { 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; } 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"); qDeleteAll( h_eaders ); h_eaders.clear(); // 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; } 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" ); } } } 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" ); } } } } } if ( ct->isText() ) { // default is text/plain return; //nothing to do } 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" ); } } } } 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; } 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 { } return newHead; } void Content::clear() { Q_D(Content); qDeleteAll( h_eaders ); h_eaders.clear(); - qDeleteAll( d->contents ); - d->contents.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 e = d->head; e += '\n'; //body if ( !d->body.isEmpty() ) { //this message contains only one part Headers::ContentTransferEncoding *enc=contentTransferEncoding(); 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 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 // first we convert the body to a content Content *main = new Content( this ); //the Mime-Headers are needed, so we move them to the new 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 it = h_eaders.erase( it ); } else { ++it; } } //"main" is now part of a multipart/mixed message 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; main->setBody( d->body ); d->contents.append( main ); d->body.clear(); //no longer needed //finally we have to convert this article to "multipart/mixed" Headers::ContentType *ct=contentType(); ct->setMimeType( "multipart/mixed" ); ct->setBoundary( multiPartBoundary() ); ct->setCategory( Headers::CCcontainer ); contentTransferEncoding()->clear(); // 7Bit, decoded } //here we actually add the content if ( prepend ) { d->contents.insert( 0, c ); } else { d->contents.append( c ); } if ( c->parent() != this ) c->setParent(this); } void Content::removeContent( Content *c, bool del ) { Q_D(Content); if ( d->contents.isEmpty() ) { // what the .. return; } d->contents.removeAll( c ); if ( del ) { delete c; } else { c->setParent( 0 ); } //only one content left => turn this message in 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; } } //now we can copy the body d->body = main->body(); //finally we can delete the content list qDeleteAll( d->contents ); d->contents.clear(); } } void Content::changeEncoding( Headers::contentEncoding e ) { Headers::ContentTransferEncoding *enc = contentTransferEncoding(); 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 } 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 d_ptr->body = KCodecs::base64Encode( decodedContent(), true ); d_ptr->body.append( "\n" ); enc->setEncoding( e ); //set encoding enc->setDecoded( 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 ); } 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 *Content::getHeaderByType( const char *type ) { return headerByType( type ); } Headers::Base *Content::headerByType( const char *type ) { if ( !type ) { return 0; } //first we check if the requested header is already cached foreach ( Headers::Base *h, h_eaders ) { if ( h->is( type ) ) { 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 } } QList Content::headersByType( const char *type ) { QList result; if ( !type ) { return result; } 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; } removeHeader( h->type() ); h_eaders.append( h ); } 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 } 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 { 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"; }; return content; } } // namespace KMime diff --git a/kmime/kmime_content.h b/kmime/kmime_content.h index 1a5dedd7d..2e18a0f31 100644 --- a/kmime/kmime_content.h +++ b/kmime/kmime_content.h @@ -1,488 +1,502 @@ /* 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 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. */ class KMIME_EXPORT Content { public: 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 /** 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 /** 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. */ virtual void parse(); /** Call to generate the MIME structure of the message. */ virtual void assemble(); /** Clears the complete message and deletes all sub-Contents. */ 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 @param head is a QByteArray containing the header data. */ Headers::Generic *nextHeader( QByteArray &head ); /** Tries to find a @p type header in the message and returns it. @deprecated Use headerByType( const char * ) */ KDE_DEPRECATED virtual Headers::Base *getHeaderByType( const char *type ); /** Tries to find a @p type header in the message and returns it. @since 4.2 */ virtual Headers::Base *headerByType( const char *type ); /** 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 ); virtual void setHeader( Headers::Base *h ); virtual bool removeHeader( const char *type ); 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 size of the Content body after encoding. */ 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. @param c The new sub-Content. @param prepend if true, prepend to the Content list; else append to the Content list. @see removeContent(). */ void addContent( Content *c, bool prepend = false ); /** - Removes the given sub-Content, the current Content object is converted - into a single-port Content if only one sub-Content is left. + Removes the given sub-Content. The current Content object is converted + into a single-part Content if only one sub-Content is left. @param c The Content to remove. @param del if true, delete the removed Content object. Otherwise its parent is set to NULL. @see addContent(). + @see clearContents(). */ void removeContent( Content *c, bool del = false ); 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 ); /** 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 is invalid (empty), this Content is returned. @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. @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 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; bool decodeText(); template T *headerInstance( T *ptr, bool create ); 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() 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 ); } return ptr; } } // namespace KMime #endif // __KMIME_CONTENT_H__ diff --git a/kmime/kmime_headers.h b/kmime/kmime_headers.h index 3e416e915..eff454e5c 100644 --- a/kmime/kmime_headers.h +++ b/kmime/kmime_headers.h @@ -1,1460 +1,1466 @@ /* -*- 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, - CE8Bit, - CEquPr, - CEbase64, - CEuuenc, - CEbinary + CE7Bit, ///< 7bit + CE8Bit, ///< 8bit + CEquPr, ///< quoted-printable + CEbase64, ///< base64 + CEuuenc, ///< uuencode + CEbinary ///< binary }; +/** + Various possible values for the "Content-Disposition" header. +*/ enum contentDisposition { - CDInvalid, - CDinline, - CDattachment, - CDparallel + 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(); #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; /** 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 ); bool decoded() const; void setDecoded( bool decoded = true ); 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 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/tests/CMakeLists.txt b/kmime/tests/CMakeLists.txt index c1e00ac76..a2e69cdb1 100644 --- a/kmime/tests/CMakeLists.txt +++ b/kmime/tests/CMakeLists.txt @@ -1,71 +1,72 @@ 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_message_test.cpp) diff --git a/kmime/tests/kmime_charfreq_test.cpp b/kmime/tests/kmime_charfreq_test.cpp new file mode 100644 index 000000000..fc6d9299b --- /dev/null +++ b/kmime/tests/kmime_charfreq_test.cpp @@ -0,0 +1,154 @@ +/* + 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_charfreq_test.h" +#include + +#include + +#include +using namespace KMime; + +QTEST_KDEMAIN( KMimeCharFreqTest, NoGUI ) + +void KMimeCharFreqTest::test8bitData() +{ + { + // If it has NUL then it's Binary (equivalent to EightBitData in CharFreq). + QByteArray data( "123" ); + data += char( 0 ); + data += "test"; + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::Binary ); + } + + { + // If it has lines longer than 998, it's EightBitData. + QByteArray data; + for( int i = 0; i < 999; i++ ) { + data += char( 169 ); + } + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::EightBitData ); + } + + { + // If #CR != #CRLF then it's EightBitData. + QByteArray data( "©line1\r\nline2\r" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::EightBitData ); + } + + { + // If #LF != #CRLF then it's EightBitData. + QByteArray data( "©line1\r\nline2\n" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::EightBitData ); + } + + { + // If it has a lot of control chars, it's EightBitData. + QByteArray data( "©test\a\a\a\a\a\a\a" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::EightBitData ); + } +} + +void KMimeCharFreqTest::test8bitText() +{ + { + // If it has no NULs, few CTLs, no stray CRs or LFs, it's EightBitText. + QByteArray data( "©beware the beast but enjoy the feast he offers...\r\n" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::EightBitText ); + } +} + +void KMimeCharFreqTest::test7bitData() +{ + { + // If it has lines longer than 998, it's SevenBitData. + QByteArray data; + for( int i = 0; i < 999; i++ ) { + data += 'a'; + } + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::SevenBitData ); + } + + { + // If #CR != #CRLF then it's SevenBitData. + QByteArray data( "line1\r\nline2\r" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::SevenBitData ); + } + + { + // If #LF != #CRLF then it's SevenBitData. + QByteArray data( "line1\r\nline2\n" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::SevenBitData ); + } + + { + // If it has a lot of control chars, it's SevenBitData. + QByteArray data( "test\a\a\a\a\a\a\a" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::SevenBitData ); + } +} + +void KMimeCharFreqTest::test7bitText() +{ + { + // If it has no NULs, few CTLs, no stray CRs or LFs, it's SevenBitText. + QByteArray data( "beware the beast but enjoy the feast he offers...\r\n" ); + kDebug() << data; + CharFreq cf( data ); + QCOMPARE( cf.type(), CharFreq::SevenBitText ); + } +} + +void KMimeCharFreqTest::testTrailingWhitespace() +{ + QByteArray data( "test " ); + kDebug() << data; + CharFreq cf( data ); + QVERIFY( cf.hasTrailingWhitespace() ); +} + +void KMimeCharFreqTest::testLeadingFrom() +{ + QByteArray data( "From here thither" ); + kDebug() << data; + CharFreq cf( data ); + QVERIFY( cf.hasLeadingFrom() ); +} + +#include "kmime_charfreq_test.moc" diff --git a/kmime/tests/kmime_charfreq_test.h b/kmime/tests/kmime_charfreq_test.h new file mode 100644 index 000000000..dae220be3 --- /dev/null +++ b/kmime/tests/kmime_charfreq_test.h @@ -0,0 +1,36 @@ +/* + 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_CHARSET_TEST_H +#define KMIME_CHARSET_TEST_H + +#include + +class KMimeCharFreqTest : public QObject +{ + Q_OBJECT + private Q_SLOTS: + void test8bitData(); + void test8bitText(); + void test7bitData(); + void test7bitText(); + void testTrailingWhitespace(); + void testLeadingFrom(); +}; + +#endif diff --git a/kmime/tests/kmime_message_test.cpp b/kmime/tests/kmime_message_test.cpp index 7ae8680ea..8bf4ae7d4 100644 --- a/kmime/tests/kmime_message_test.cpp +++ b/kmime/tests/kmime_message_test.cpp @@ -1,173 +1,178 @@ /* 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. */ #include "kmime_message_test.h" #include "kmime_message_test.moc" #include #include using namespace KMime; QTEST_KDEMAIN( MessageTest, NoGUI ) void MessageTest::testMainBodyPart() { Message *msg = new Message(); Message *msg2 = new Message(); Content *text = new Content(); text->contentType()->setMimeType( "text/plain" ); Content *html = new Content(); html->contentType()->setMimeType( "text/html" ); // empty message QCOMPARE( msg->mainBodyPart(), msg ); QCOMPARE( msg->mainBodyPart( "text/plain" ), (Content*)0 ); // non-multipart msg->contentType()->setMimeType( "text/html" ); QCOMPARE( msg->mainBodyPart(), msg ); QCOMPARE( msg->mainBodyPart( "text/plain" ), (Content*)0 ); QCOMPARE( msg->mainBodyPart( "text/html" ), msg ); // multipart/mixed msg2->contentType()->setMimeType( "multipart/mixed" ); msg2->addContent( text ); msg2->addContent( html ); QCOMPARE( msg2->mainBodyPart(), text ); QCOMPARE( msg2->mainBodyPart( "text/plain" ), text ); QCOMPARE( msg2->mainBodyPart( "text/html" ), (Content*)0 ); + // Careful with removing content here. If we remove one of the two contents + // (by adding it to another message), the multipart will automatically be + // converted to a single-part, deleting the other content! + msg2->clearContents( false ); + // mulitpart/alternative msg->contentType()->setMimeType( "multipart/alternative" ); msg->addContent( html ); msg->addContent( text ); QCOMPARE( msg->mainBodyPart(), html ); QCOMPARE( msg->mainBodyPart( "text/plain" ), text ); QCOMPARE( msg->mainBodyPart( "text/html" ), html ); // mulitpart/alternative inside multipart/mixed Message* msg3 = new Message(); msg3->contentType()->setMimeType( "multipart/mixed" ); msg3->addContent( msg ); Content *attach = new Content(); attach->contentType()->setMimeType( "text/plain" ); QCOMPARE( msg3->mainBodyPart(), html ); QCOMPARE( msg3->mainBodyPart( "text/plain" ), text ); QCOMPARE( msg3->mainBodyPart( "text/html" ), html ); } void MessageTest::testBrunosMultiAssembleBug() { QByteArray data = "From: Sender \n" "Subject: Sample message\n" "To: Receiver \n" "Date: Sat, 04 Aug 2007 12:44 +0200\n" "MIME-Version: 1.0\n" "Content-Type: text/plain\n" "X-Foo: bla\n" "X-Bla: foo\n" "\n" "body"; Message *msg = new Message; msg->setContent( data ); msg->parse(); msg->assemble(); QCOMPARE( msg->encodedContent(), data ); msg->inReplyTo(); msg->assemble(); QCOMPARE( msg->encodedContent(), data ); delete msg; } void MessageTest::testWillsAndTillsCrash() { QByteArray deadlyMail = "From: censored@yahoogroups.com\n" "To: censored@yahoogroups.com\n" "Sender: censored@yahoogroups.com\n" "MIME-Version: 1.0\n" "Date: 29 Jan 2006 23:58:21 -0000\n" "Subject: [censored] Birthday Reminder\n" "Reply-To: censored@yahoogroups.com\n" "Content-Type: multipart/alternative;\n boundary=\"YCalReminder=cNM4SNTGA4Cg1MVLaPpqNF1138579098\"\n" "X-Length: 9594\n" "X-UID: 6161\n" "Status: RO\n" "X-Status: OC\n" "X-KMail-EncryptionState:\n" "X-KMail-SignatureState:\n" "X-KMail-MDN-Sent:\n\n"; // QByteArray deadlyMail; // QFile f( "deadlymail" ); // f.open( QFile::ReadOnly ); // deadlyMail = f.readAll(); KMime::Message *msg = new KMime::Message; msg->setContent( deadlyMail ); msg->parse(); QVERIFY( !msg->date()->isEmpty() ); QCOMPARE( msg->subject()->as7BitString( false ), QByteArray( "[censored] Birthday Reminder" ) ); QCOMPARE( msg->from()->mailboxes().count(), 1 ); QCOMPARE( msg->sender()->mailboxes().count(), 1 ); QCOMPARE( msg->replyTo()->mailboxes().count(), 1 ); QCOMPARE( msg->to()->mailboxes().count(), 1 ); QCOMPARE( msg->cc()->mailboxes().count(), 0 ); QCOMPARE( msg->bcc()->mailboxes().count(), 0 ); QCOMPARE( msg->inReplyTo()->identifiers().count(), 0 ); QCOMPARE( msg->messageID()->identifiers().count(), 0 ); delete msg; } void MessageTest::missingHeadersTest() { // Test that the message body is OK even though some headers are missing KMime::Message msg; QString body = "Hi Donald, look at those nice pictures I found!\n"; QString content = "From: georgebush@whitehouse.org\n" "To: donaldrumsfeld@whitehouse.org\n" "Subject: Cute Kittens\n" "\n" + body; msg.setContent( content.toAscii() ); msg.parse(); msg.assemble(); QCOMPARE( body, QString::fromAscii( msg.body() ) ); // Now create a new message, based on the content of the first one. // The body of the new message should still be the same. // (there was a bug that caused missing mandatory headers to be // added as a empty newline, which caused parts of the header to // leak into the body) KMime::Message msg2; msg2.setContent( msg.encodedContent() ); msg2.parse(); msg2.assemble(); QCOMPARE( body, QString::fromAscii( msg2.body() ) ); }