diff --git a/messagecomposer/CMakeLists.txt b/messagecomposer/CMakeLists.txt index cf48111b8..87af2f068 100644 --- a/messagecomposer/CMakeLists.txt +++ b/messagecomposer/CMakeLists.txt @@ -1,52 +1,54 @@ set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}" ) add_definitions( -DQT_NO_CAST_FROM_ASCII ) add_definitions( -DQT_NO_CAST_TO_ASCII ) add_subdirectory( tests ) set( messagecomposer_src composer.cpp behaviour.cpp - attachmentpart.cpp finalmessage.cpp - infopart.cpp + util.cpp + messagepart.cpp + attachmentpart.cpp + infopart.cpp skeletonmessagejob.cpp textpart.cpp job.cpp contentjob.cpp maintextjob.cpp multipartjob.cpp ) include_directories( ${Boost_INCLUDE_DIR} ) kde4_add_library( messagecomposer SHARED ${messagecomposer_src} ) target_link_libraries( messagecomposer ${KDE4_KIO_LIBS} kmime ) set_target_properties( messagecomposer PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install( TARGETS messagecomposer EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} ) install( FILES messagecomposer_export.h composer.h behaviour.h attachmentpart.h finalmessage.h infopart.h messagepart.h skeletonmessagejob.h textpart.h job.h contentjob.h maintextjob.h multipartjob.h DESTINATION ${INCLUDE_INSTALL_DIR}/messagecomposer COMPONENT Devel ) diff --git a/messagecomposer/COMPLIANCE b/messagecomposer/COMPLIANCE new file mode 100644 index 000000000..c4e2b97bf --- /dev/null +++ b/messagecomposer/COMPLIANCE @@ -0,0 +1,37 @@ +How Content-Transfer-Encoding (CTE) is handled: +-------------------------------------------- +(see RFC2045) +* For text body and text attachments: + 1) 7bit if text fits. + 2) 8bit if text fits and transport supports it. + 3) quoted-printable or base 64 otherwise, depending on which is most space-efficient. +* For binary attachments: + 1) base64 +* For multipart/* (except signed/encrypted): + 1) 7bit if everything fits. + 2) 8bit if allowed by transport. +* For message/rfc822 (encapsulated messages): + 1) 7bit if everything fits. + 2) 8bit if allowed by transport. + TODO what if encapsulated message is 8bit but our transport is 7bit??? +* For multipart/signed (see RFC3156): + 1) 7bit if everything fits + 2) quoted-printable or base64 if: + - has trailing whitespace + - a line starts with 'From ' +* For multipart/encrypted (but not signed): + Same as for multipart/*. + + + +References: +-------------- +RFC5322: Internet Message Format (P. Resnick, Ed., October 2008) +RFC2045: Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies + (N. Freed, N. Borenstein, November 1996) +RFC2046: Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types + (N. Freed N. Borenstein, November 1996) +RFC2047: MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for + Non-ASCII Text (K. Moore, November 1996) +RFC3156: MIME Security with OpenPGP (M. Elkins, D. Del Torto, R. Levien, T. Roessler, August 2001) + diff --git a/messagecomposer/PLAN b/messagecomposer/PLAN deleted file mode 100644 index 1491a310f..000000000 --- a/messagecomposer/PLAN +++ /dev/null @@ -1,38 +0,0 @@ -1) Make it work for plain text (including charset, word wrapping stuff) -2) Figure out headers (groups, display names, empty to:, local usernames, comments?) -3) HTML -4) Transport -5) Attachments -6) Signing & Crypto -+tests at every step. - -Questions: -* Plain messages with only us-ascii characters: KMail makes them MIME messages - anyway. Do we ever want to send a non-MIME message? -* When is the "default" charset used? -* If multi-recipient-crypto, save all in sent-mail or just one unencrypted copy? - KMail seems to save the encrypted stuff, but then how can it be decrypted - without someone's private key? Or does KMail save a copy encrypted with my own - key??? --> Now I think that's exactly what it does. -* What should be in namespace KMail? -* Do we ever want to send a html-only message with no plaintext alternative? -* Why is KeyResolver in KMail and not kleopatra / libkleo??? -* Can KMime::Message::Ptr be forward-declared somehow? -* I have no clue how to make this extensible... and Behaviour is already a problem. -* What is the policy on Content-Transfer-Encoding? -* When should I have a virtual destructor if it's empty? (I guess only then someone - might subclass...) -* Disable copy constructors of jobs... -* Should drafts be encrypted to self? -* Figure out if MessagePart & family need to be qobjects at all... -* Currently MessagePart is a useless empty base class. -* I get an error from boost if I don't enable exceptions. But KMime seems to work - without them just fine. WTF? -* MessageComposer::Job and Akonadi::Job collide... -* The errors are defined in Job and Composer does not inherit that. I don't want - to make it inherit Job because it is really different from a Job (it doesn't - create a content, for starters). But then apps need to compare with - Job::SomeError instead of Composer::SomeError, and need to include job.h just for - that :-( -* Copying headers, contents, messages... is tricky. -* What is a good guideline for when I should call Content::assemble()? diff --git a/messagecomposer/TODO.cberzan b/messagecomposer/TODO.cberzan index dbc3f26e3..6158e19b0 100644 --- a/messagecomposer/TODO.cberzan +++ b/messagecomposer/TODO.cberzan @@ -1,3 +1,26 @@ -Design: -* Lots of duplication between AttachmentStrategy, AlternativeStrategy, - RelatedStrategy on one hand, and GpgSigned, GpgEncrypted on the other hand... +1) Make it work for plain text (including charset, word wrapping stuff) +2) Figure out headers (groups, display names, empty to:, local usernames, comments?) +3) HTML +4) Transport +5) Attachments +6) Signing & Crypto ++tests at every step. + +Questions: +* If multi-recipient-crypto, save all in sent-mail or just one unencrypted copy? + KMail seems to save the encrypted stuff, but then how can it be decrypted + without someone's private key? Or does KMail save a copy encrypted with my own + key??? --> Now I think that's exactly what it does. +* Why is KeyResolver in KMail and not kleopatra / libkleo??? +* Can KMime::Message::Ptr be forward-declared somehow? +* I have no clue how to make this extensible... and Behaviour is already a problem. +* Disable copy constructors of jobs... +* Should drafts be encrypted to self? +* Figure out if MessagePart & family need to be qobjects at all... +* MessageComposer::Job and Akonadi::Job collide... +* The errors are defined in Job and Composer does not inherit that. I don't want + to make it inherit Job because it is really different from a Job (it doesn't + create a content, for starters). But then apps need to compare with + Job::SomeError instead of Composer::SomeError, and need to include job.h just for + that :-( +* Think of where shared_ptrs should be used. diff --git a/messagecomposer/attachmentpart.h b/messagecomposer/attachmentpart.h index 7ac36807a..6a15c8528 100644 --- a/messagecomposer/attachmentpart.h +++ b/messagecomposer/attachmentpart.h @@ -1,56 +1,58 @@ /* 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 MESSAGECOMPOSER_ATTACHMENTPART_H #define MESSAGECOMPOSER_ATTACHMENTPART_H #include "messagepart.h" #include class KUrl; namespace MessageComposer { +/** setOverrideTransferEncoding for an AttachmentPart means setting the CTE for the sub-Content + representing this attachment */ class MESSAGECOMPOSER_EXPORT AttachmentPart : public MessagePart { Q_OBJECT public: typedef QList List; explicit AttachmentPart( QObject *parent = 0 ); virtual ~AttachmentPart(); KUrl url() const; void setUrl( const KUrl &url ); bool isDataLoaded() const; bool loadData(); // TODO handle mime type; charset for textual types, etc. private: class Private; Private *const d; }; } // namespace MessageComposer #endif // MESSAGECOMPOSER_INFOPART_H diff --git a/messagecomposer/behaviour.cpp b/messagecomposer/behaviour.cpp index a95fd9aa5..76167b6d5 100644 --- a/messagecomposer/behaviour.cpp +++ b/messagecomposer/behaviour.cpp @@ -1,123 +1,127 @@ /* 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 "behaviour.h" using namespace MessageComposer; Behaviour::Behaviour() : d( new Private ) { // Default behaviour for sending. d->actions[ UseGui ] = true; d->actions[ UseCrypto ] = true; d->actions[ UseWrapping ] = true; d->actions[ UseFallbackCharset ] = false; d->actions[ WarnBadCharset ] = true; d->actions[ WarnZeroRecipients ] = true; d->actions[ CustomHeaders ] = false; + d->actions[ EightBitTransport ] = false; } Behaviour::~Behaviour() { // d is a QSharedDataPointer. } bool Behaviour::isActionEnabled( Action action ) const { Q_ASSERT( action >= 0 && action < LastAction ); return d->actions[ action ]; } void Behaviour::enableAction( Action action, bool enable ) { Q_ASSERT( action >= 0 && action < LastAction ); d->actions[ action ] = enable; } void Behaviour::disableAction( Action action ) { Q_ASSERT( action >= 0 && action < LastAction ); d->actions[ action ] = false; } //static Behaviour Behaviour::behaviourForSending() { static Behaviour beh; // A default-constructed Behaviour has default sending behaviour. return beh; } //static Behaviour Behaviour::behaviourForPrinting() { static bool init = false; static Behaviour beh; if( !init ) { beh.d->actions[ UseGui ] = true; beh.d->actions[ UseCrypto ] = false; beh.d->actions[ UseWrapping ] = true; beh.d->actions[ UseFallbackCharset ] = false; beh.d->actions[ WarnBadCharset ] = true; beh.d->actions[ WarnZeroRecipients ] = false; beh.d->actions[ CustomHeaders ] = false; + beh.d->actions[ EightBitTransport ] = false; init = true; } return beh; } //static Behaviour Behaviour::behaviourForAutosaving() { static bool init = false; static Behaviour beh; if( !init ) { beh.d->actions[ UseGui ] = false; beh.d->actions[ UseCrypto ] = false; beh.d->actions[ UseWrapping ] = false; beh.d->actions[ UseFallbackCharset ] = true; beh.d->actions[ WarnBadCharset ] = false; beh.d->actions[ WarnZeroRecipients ] = false; beh.d->actions[ CustomHeaders ] = true; + beh.d->actions[ EightBitTransport ] = false; init = true; } return beh; } //static Behaviour Behaviour::behaviourForSavingLocally() { static bool init = false; static Behaviour beh; if( !init ) { beh.d->actions[ UseGui ] = true; beh.d->actions[ UseCrypto ] = false; beh.d->actions[ UseWrapping ] = false; beh.d->actions[ UseFallbackCharset ] = false; beh.d->actions[ WarnBadCharset ] = true; beh.d->actions[ WarnZeroRecipients ] = false; beh.d->actions[ CustomHeaders ] = true; + beh.d->actions[ EightBitTransport ] = false; init = true; } return beh; } diff --git a/messagecomposer/behaviour.h b/messagecomposer/behaviour.h index 274b04bb1..2fc9c0ab6 100644 --- a/messagecomposer/behaviour.h +++ b/messagecomposer/behaviour.h @@ -1,95 +1,96 @@ /* 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 MESSAGECOMPOSER_BEHAVIOUR_H #define MESSAGECOMPOSER_BEHAVIOUR_H #include "messagecomposer_export.h" #include namespace MessageComposer { /** */ class MESSAGECOMPOSER_EXPORT Behaviour { public: enum Action { UseGui, UseCrypto, UseWrapping, UseFallbackCharset, WarnBadCharset, WarnZeroRecipients, CustomHeaders, + EightBitTransport, LastAction // TODO should this be made =100 for further expansion? }; Behaviour(); virtual ~Behaviour(); bool isActionEnabled( Action action ) const; void enableAction( Action action, bool enable = true ); void disableAction( Action action ); static Behaviour behaviourForSending(); static Behaviour behaviourForPrinting(); static Behaviour behaviourForAutosaving(); static Behaviour behaviourForSavingLocally(); private: class Private; QSharedDataPointer d; }; /* FIXME I can't figure out how else to make this work. If I put it in the cpp or in another file like behaviour_p.h, I get 'incomplete type' errors. If I put it in behavior_p.h and include it in the bottom, I need to install the _p anyway. The problem seems to be that Behaviour needs Behaviour::Private (for QSharedDataPointer), but Behaviour::Private needs Behaviour back (for Behaviour:LastAction). -> I removed the latter dependency and the problem persists :-/ */ /** @internal */ class Behaviour::Private : public QSharedData { public: Private() { } Private( const Private &other ) : QSharedData( other ) { for( int i = 0; i < Behaviour::LastAction; i++ ) { actions[i] = other.actions[i]; } } bool actions[ Behaviour::LastAction ]; }; } #endif diff --git a/messagecomposer/contentjob.cpp b/messagecomposer/contentjob.cpp index 69a9c2679..36af8fdb2 100644 --- a/messagecomposer/contentjob.cpp +++ b/messagecomposer/contentjob.cpp @@ -1,127 +1,209 @@ /* 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 "contentjob.h" + +#include "composer.h" #include "job_p.h" +#include "util.h" #include +#include +#include #include #include using namespace MessageComposer; using namespace KMime; class MessageComposer::ContentJobPrivate : public JobPrivate { public: ContentJobPrivate( ContentJob *qq ) : JobPrivate( qq ) , contentDisposition( 0 ) , contentTransferEncoding( 0 ) , contentType( 0 ) { } + bool chooseCTE(); + QByteArray data; Headers::ContentDisposition *contentDisposition; Headers::ContentTransferEncoding *contentTransferEncoding; Headers::ContentType *contentType; + + Q_DECLARE_PUBLIC( ContentJob ) }; +bool ContentJobPrivate::chooseCTE() +{ + // Based on KMail code by: + // Copyright 2009 Thomas McGuire + + Q_Q( ContentJob ); + Q_ASSERT( composer ); + QList allowed; + CharFreq cf( data ); + + switch ( cf.type() ) { + case CharFreq::SevenBitText: + allowed << Headers::CE7Bit; + case CharFreq::EightBitText: + if ( composer->behaviour().isActionEnabled( Behaviour::EightBitTransport ) ) + allowed << Headers::CE8Bit; + case CharFreq::SevenBitData: + if ( cf.printableRatio() > 5.0/6.0 ) { + // let n the length of data and p the number of printable chars. + // Then base64 \approx 4n/3; qp \approx p + 3(n-p) + // => qp < base64 iff p > 5n/6. + allowed << Headers::CEquPr; + allowed << Headers::CEbase64; + } else { + allowed << Headers::CEbase64; + allowed << Headers::CEquPr; + } + break; + case CharFreq::EightBitData: + allowed << Headers::CEbase64; + break; + case CharFreq::None: + default: + Q_ASSERT( false ); + } + +#if 0 //TODO signing + // In the following cases only QP and Base64 are allowed: + // - the buffer will be OpenPGP/MIME signed and it contains trailing + // whitespace (cf. RFC 3156) + // - a line starts with "From " + if ( ( willBeSigned && cf.hasTrailingWhitespace() ) || + cf.hasLeadingFrom() ) { + ret.removeAll( DwMime::kCte8bit ); + ret.removeAll( DwMime::kCte7bit ); + } +#endif + + if( contentTransferEncoding ) { + // Specific CTE set. Check that our data fits in it. + if( !allowed.contains( contentTransferEncoding->encoding() ) ) { + q->setError( Job::BugError ); + q->setErrorText( i18n( "%1 Content-Transfer-Encoding cannot correctly encode this message.", + nameForEncoding( contentTransferEncoding->encoding() ) ) ); + return false; + } + } else { + // No specific CTE set. Choose the best one. + Q_ASSERT( !allowed.isEmpty() ); + contentTransferEncoding = new Headers::ContentTransferEncoding; + contentTransferEncoding->setEncoding( allowed.first() ); + } + kDebug() << "Settled on encoding" << nameForEncoding( contentTransferEncoding->encoding() ); + return true; +} + ContentJob::ContentJob( QObject *parent ) : Job( *new ContentJobPrivate( this ), parent ) { } ContentJob::~ContentJob() { } QByteArray ContentJob::data() const { Q_D( const ContentJob ); return d->data; } void ContentJob::setData( const QByteArray &data ) { Q_D( ContentJob ); d->data = data; } Headers::ContentDisposition *ContentJob::contentDisposition() { Q_D( ContentJob ); if( !d->contentDisposition ) { d->contentDisposition = new Headers::ContentDisposition; } return d->contentDisposition; } Headers::ContentTransferEncoding *ContentJob::contentTransferEncoding() { Q_D( ContentJob ); if( !d->contentTransferEncoding ) { d->contentTransferEncoding = new Headers::ContentTransferEncoding; } return d->contentTransferEncoding; } Headers::ContentType *ContentJob::contentType() { Q_D( ContentJob ); if( !d->contentType ) { d->contentType = new Headers::ContentType; } return d->contentType; } void ContentJob::process() { Q_D( ContentJob ); Q_ASSERT( d->resultContent == 0 ); // Not processed before. d->resultContent = new Content; + + if( !d->chooseCTE() ) { + Q_ASSERT( error() ); + emitResult(); + return; + } - // Headers. + // Set headers. if( d->contentDisposition ) { d->resultContent->setHeader( d->contentDisposition ); d->contentDisposition->setParent( d->resultContent ); } - if( d->contentTransferEncoding ) { + Q_ASSERT( d->contentTransferEncoding ); // chooseCTE() created it if it didn't exist. + { d->resultContent->setHeader( d->contentTransferEncoding ); d->contentTransferEncoding->setParent( d->resultContent ); kDebug() << "decoded" << d->contentTransferEncoding->decoded() << "needToEncode" << d->contentTransferEncoding->needToEncode(); } if( d->contentType ) { d->resultContent->setHeader( d->contentType ); d->contentType->setParent( d->resultContent ); } - // Data. + // Set data. d->resultContent->setBody( d->data ); kDebug() << "encoded content" << d->resultContent->encodedContent(); emitResult(); } #include "contentjob.moc" diff --git a/messagecomposer/infopart.h b/messagecomposer/infopart.h index bb4ccc081..90beadc9b 100644 --- a/messagecomposer/infopart.h +++ b/messagecomposer/infopart.h @@ -1,62 +1,63 @@ /* 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 MESSAGECOMPOSER_INFOPART_H #define MESSAGECOMPOSER_INFOPART_H #include "messagepart.h" #include #include #include namespace MessageComposer { +/** setOverrideTransferEncoding for an InfoPart has no effect */ class MESSAGECOMPOSER_EXPORT InfoPart : public MessagePart { Q_OBJECT public: explicit InfoPart( QObject *parent = 0 ); virtual ~InfoPart(); QString from() const; void setFrom( const QString &from ); QStringList to() const; void setTo( const QStringList &to ); QStringList cc() const; void setCc( const QStringList &cc ); QStringList bcc() const; void setBcc( const QStringList &bcc ); QString subject() const; void setSubject( const QString &subject ); int transportId() const; void setTransportId( int tid ); private: class Private; Private *const d; }; } // namespace MessageComposer #endif // MESSAGECOMPOSER_INFOPART_H diff --git a/messagecomposer/maintextjob.cpp b/messagecomposer/maintextjob.cpp index 00cc6e943..681a872d1 100644 --- a/messagecomposer/maintextjob.cpp +++ b/messagecomposer/maintextjob.cpp @@ -1,284 +1,283 @@ /* 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 "maintextjob.h" #include "composer.h" #include "contentjob.h" #include "job_p.h" #include "textpart.h" #include #include #include #include #include #include #include #include using namespace MessageComposer; using namespace KMime; class MessageComposer::MainTextJobPrivate : public JobPrivate { public: MainTextJobPrivate( MainTextJob *qq ) : JobPrivate( qq ) , textPart( 0 ) { } bool chooseSourcePlainText(); bool chooseCharsetAndEncode(); bool chooseCharset(); void encodeTexts(); TextPart *textPart; QList charsets; QByteArray chosenCharset; QString sourcePlainText; QByteArray encodedPlainText; QByteArray encodedHtml; // TODO related images Q_DECLARE_PUBLIC( MainTextJob ) }; bool MainTextJobPrivate::chooseSourcePlainText() { Q_Q( MainTextJob ); Q_ASSERT( composer ); Q_ASSERT( textPart ); if( composer->behaviour().isActionEnabled( Behaviour::UseWrapping ) ) { sourcePlainText = textPart->wrappedPlainText(); if( sourcePlainText.isEmpty() && !textPart->cleanPlainText().isEmpty() ) { q->setError( Job::BugError ); q->setErrorText( i18n( "Asked to use word wrapping, but given no wrapped plain text." ) ); return false; } } else { sourcePlainText = textPart->cleanPlainText(); if( sourcePlainText.isEmpty() && !textPart->wrappedPlainText().isEmpty() ) { q->setError( Job::BugError ); q->setErrorText( i18n( "Asked not to use word wrapping, but given no clean plain text." ) ); return false; } } return true; } bool MainTextJobPrivate::chooseCharsetAndEncode() { Q_Q( MainTextJob ); Q_ASSERT( composer ); const Behaviour &beh = composer->behaviour(); Q_ASSERT( textPart ); charsets = textPart->charsets(); foreach( const QByteArray &name, textPart->charsets() ) { charsets << name.toLower(); } if( beh.isActionEnabled( Behaviour::UseFallbackCharset ) ) { charsets << "utf-8"; // TODO somehow save the chosen charset in a custom header if behaviour allows it... } if( charsets.isEmpty() ) { q->setError( Job::BugError ); q->setErrorText( i18n( "No charsets were available for encoding," " and the fallback charset was disabled." ) ); return false; } if( chooseCharset() ) { // Good, one of the charsets can encode the data without loss. encodeTexts(); return true; } else { // No good charset was found. if( beh.isActionEnabled( Behaviour::UseGui ) && beh.isActionEnabled( Behaviour::WarnBadCharset ) ) { // Warn the user and give them a chance to go back. int result = KMessageBox::warningYesNo( composer->parentWidget(), i18n( "Encoding the message with %1 will lose some characters.\n" "Do you want to continue?", QString::fromLatin1( charsets.first() ) ), i18n( "Some Characters Will Be Lost" ), KGuiItem( i18n("Lose Characters") ), KGuiItem( i18n("Change Encoding") ) ); if( result == KMessageBox::No ) { q->setError( Job::UserCancelledError ); q->setErrorText( i18n( "User decided to change the encoding." ) ); return false; } else { chosenCharset = charsets.first(); encodeTexts(); return true; } } else if( beh.isActionEnabled( Behaviour::WarnBadCharset ) ) { // Should warn user but no Gui available. kDebug() << "WarnBadCharset but not UseGui."; q->setError( Job::UserError ); q->setErrorText( i18n( "The selected encoding (%1) cannot fully encode the message.", QString::fromLatin1( charsets.first() ) ) ); return false; } else { // OK to go ahead with a bad charset. chosenCharset = charsets.first(); encodeTexts(); return true; // FIXME: This is based on the assumption that QTextCodec will replace // unknown characters with '?' or some other meaningful thing. The code in // QTextCodec indeed uses '?', but this behaviour is not documented. } } // Should not reach here. Q_ASSERT( false ); return false; } bool MainTextJobPrivate::chooseCharset() { Q_ASSERT( !charsets.isEmpty() ); Q_ASSERT( textPart ); QString toTry = sourcePlainText; if( textPart->isHtmlUsed() ) { toTry = textPart->cleanHtml(); } foreach( const QByteArray &name, charsets ) { // We use KCharsets::codecForName() instead of QTextCodec::codecForName() here, because // the former knows us-ascii is latin1. QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( name ) ); if( !codec ) { kWarning() << "Could not get text codec for charset" << name; continue; } if( codec->canEncode( toTry ) ) { // Special check for us-ascii (needed because us-ascii is not exactly latin1). if( name == "us-ascii" && !isUsAscii( toTry ) ) { continue; } kDebug() << "Chosen charset" << name; chosenCharset = name; return true; } } kDebug() << "No appropriate charset found."; return false; } void MainTextJobPrivate::encodeTexts() { Q_Q( MainTextJob ); QTextCodec *codec = KGlobal::charsets()->codecForName( QString::fromLatin1( chosenCharset ) ); if( !codec ) { kError() << "Could not get text codec for charset" << chosenCharset; q->setError( Job::BugError ); q->setErrorText( i18n( "Could not get text codec for charset \"%1\".", QString::fromLatin1( chosenCharset ) ) ); return; } encodedPlainText = codec->fromUnicode( sourcePlainText ); encodedHtml = codec->fromUnicode( textPart->cleanHtml() ); kDebug() << "Done."; } MainTextJob::MainTextJob( TextPart *textPart, QObject *parent ) : Job( *new MainTextJobPrivate( this ), parent ) { Q_D( MainTextJob ); d->textPart = textPart; } MainTextJob::~MainTextJob() { } TextPart *MainTextJob::textPart() const { Q_D( const MainTextJob ); return d->textPart; } void MainTextJob::setTextPart( TextPart *part ) { Q_D( MainTextJob ); d->textPart = part; } void MainTextJob::doStart() { Q_D( MainTextJob ); Q_ASSERT( d->textPart ); Q_ASSERT( d->composer ); // Word wrapping. if( !d->chooseSourcePlainText() ) { // chooseSourcePlainText has set an error. Q_ASSERT( error() ); emitResult(); return; } // Charset. - if( d->chooseCharsetAndEncode() ) { - // Encoding was successful. The user and we are happy with the charset, even if it may - // lose characters. - if( d->encodedHtml.isEmpty() ) { - kDebug() << "Making text/plain"; - // Content is text/plain. - ContentJob *cjob = new ContentJob( this ); - cjob->contentType()->setMimeType( "text/plain" ); - cjob->contentType()->setCharset( d->chosenCharset ); - cjob->setData( d->encodedPlainText ); - - // TODO temporary until I figure out what the CTE policy is. - cjob->contentTransferEncoding()->setEncoding( Headers::CEquPr ); - } else { - // TODO Handle multipart/alternative and multipart/related. - Q_ASSERT( false ); - } - Job::doStart(); - } else { + if( !d->chooseCharsetAndEncode() ) { // chooseCharsetAndEncode has set an error. Q_ASSERT( error() ); emitResult(); return; } + + // Assemble the Content. + if( d->encodedHtml.isEmpty() ) { + kDebug() << "Making text/plain"; + // Content is text/plain. + ContentJob *cjob = new ContentJob( this ); + cjob->contentType()->setMimeType( "text/plain" ); + cjob->contentType()->setCharset( d->chosenCharset ); + cjob->setData( d->encodedPlainText ); + if( !d->textPart->isAutoTransferEncoding() ) { + cjob->contentTransferEncoding()->setEncoding( d->textPart->overrideTransferEncoding() ); + } + } else { + // TODO Handle multipart/alternative and multipart/related. + Q_ASSERT( false ); + } + Job::doStart(); } void MainTextJob::process() { Q_D( MainTextJob ); // The content has been created by our subjob. Q_ASSERT( d->subjobContents.count() == 1 ); d->resultContent = d->subjobContents.first(); emitResult(); } #include "maintextjob.moc" diff --git a/messagecomposer/messagepart.cpp b/messagecomposer/messagepart.cpp index d69a811a0..e0699d3e9 100644 --- a/messagecomposer/messagepart.cpp +++ b/messagecomposer/messagepart.cpp @@ -1,44 +1,72 @@ /* 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 "messagepart.h" #include +using namespace KMime; using namespace MessageComposer; class MessagePart::Private { public: + Private() + : autoCTE( true ) + { + } + + bool autoCTE; + Headers::contentEncoding cte; }; MessagePart::MessagePart( QObject *parent ) : QObject( parent ) , d( new Private ) { } MessagePart::~MessagePart() { delete d; } +bool MessagePart::isAutoTransferEncoding() const +{ + return d->autoCTE; +} + +KMime::Headers::contentEncoding MessagePart::overrideTransferEncoding() const +{ + if( d->autoCTE ) { + kWarning() << "Called when CTE is auto."; + return Headers::CEbinary; + } + return d->cte; +} + +void MessagePart::setOverrideTransferEncoding( KMime::Headers::contentEncoding cte ) +{ + d->autoCTE = false; + d->cte = cte; +} + #include "messagepart.moc" diff --git a/messagecomposer/messagepart.h b/messagecomposer/messagepart.h index 7cb3c6a5b..4ad60f2bf 100644 --- a/messagecomposer/messagepart.h +++ b/messagecomposer/messagepart.h @@ -1,49 +1,55 @@ /* 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 MESSAGECOMPOSER_MESSAGEPART_H #define MESSAGECOMPOSER_MESSAGEPART_H #include "behaviour.h" #include "messagecomposer_export.h" #include +#include + namespace MessageComposer { class MessagePartPrivate; /** */ class MESSAGECOMPOSER_EXPORT MessagePart : public QObject { Q_OBJECT public: MessagePart( QObject *parent = 0 ); virtual ~MessagePart(); + bool isAutoTransferEncoding() const; + KMime::Headers::contentEncoding overrideTransferEncoding() const; + void setOverrideTransferEncoding( KMime::Headers::contentEncoding cte ); + private: class Private; Private *const d; }; } #endif diff --git a/messagecomposer/multipartjob.cpp b/messagecomposer/multipartjob.cpp index 94c17c0ba..ebc98bf38 100644 --- a/messagecomposer/multipartjob.cpp +++ b/messagecomposer/multipartjob.cpp @@ -1,79 +1,83 @@ /* 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 "multipartjob.h" #include "job_p.h" #include #include using namespace MessageComposer; using namespace KMime; class MessageComposer::MultipartJobPrivate : public JobPrivate { public: MultipartJobPrivate( MultipartJob *qq ) : JobPrivate( qq ) { } QByteArray subtype; }; MultipartJob::MultipartJob( QObject *parent ) : Job( *new MultipartJobPrivate( this ), parent ) { } MultipartJob::~MultipartJob() { } QByteArray MultipartJob::multipartSubtype() const { Q_D( const MultipartJob ); return d->subtype; } void MultipartJob::setMultipartSubtype( const QByteArray &subtype ) { Q_D( MultipartJob ); d->subtype = subtype; } void MultipartJob::process() { Q_D( MultipartJob ); Q_ASSERT( d->resultContent == 0 ); // Not processed before. Q_ASSERT( !d->subtype.isEmpty() ); d->resultContent = new Content; d->resultContent->contentType( true )->setMimeType( "multipart/" + d->subtype ); d->resultContent->contentType()->setBoundary( KMime::multiPartBoundary() ); + d->resultContent->contentTransferEncoding()->setEncoding( Headers::CE7Bit ); foreach( Content *c, d->subjobContents ) { d->resultContent->addContent( c ); + if( c->contentTransferEncoding()->encoding() == Headers::CE8Bit ) { + d->resultContent->contentTransferEncoding()->setEncoding( Headers::CE8Bit ); + } } kDebug() << "Created" << d->resultContent->contentType()->name() << "content with" << d->resultContent->contents().count() << "subjobContents."; emitResult(); } #include "multipartjob.moc" diff --git a/messagecomposer/tests/CMakeLists.txt b/messagecomposer/tests/CMakeLists.txt index dd54ebc78..a21b1cdd7 100644 --- a/messagecomposer/tests/CMakeLists.txt +++ b/messagecomposer/tests/CMakeLists.txt @@ -1,27 +1,28 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories ( ${Boost_INCLUDE_DIR} ) # convenience macro to add messagecomposer unit tests macro( add_messagecomposer_test _source ) set( _test ${_source} ) get_filename_component( _name ${_source} NAME_WE ) kde4_add_unit_test( ${_name} TESTNAME messagecomposer-${_name} ${_test} ) target_link_libraries( ${_name} kmime messagecomposer ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} ${KDE4_KDEUI_LIBS} ) endmacro( add_messagecomposer_test ) # Utility stuff. add_messagecomposer_test( behaviourtest.cpp ) # Basic jobs. add_messagecomposer_test( contentjobtest.cpp ) add_messagecomposer_test( multipartjobtest.cpp ) # More complex jobs. add_messagecomposer_test( skeletonmessagejobtest.cpp ) add_messagecomposer_test( maintextjobtest.cpp ) # Composer. +add_messagecomposer_test( composertest.cpp ) diff --git a/messagecomposer/tests/composertest.cpp b/messagecomposer/tests/composertest.cpp new file mode 100644 index 000000000..c41e0df83 --- /dev/null +++ b/messagecomposer/tests/composertest.cpp @@ -0,0 +1,76 @@ +/* + 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 "composertest.h" + +#include +#include + +#include +using namespace KMime; + +#include +#include +#include +using namespace MessageComposer; + +QTEST_KDEMAIN( ComposerTest, NoGUI ) + +void ComposerTest::testCTEErrors() +{ + // First test that the composer succeeds at all. + { + Composer *composer = new Composer; + composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); + composer->infoPart()->setFrom( QString::fromLatin1( "me@me.me" ) ); + composer->infoPart()->setTo( QStringList( QString::fromLatin1( "you@you.you" ) ) ); + composer->textPart()->setWrappedPlainText( QString::fromLatin1( "sample content" ) ); + QVERIFY( composer->exec() ); + QCOMPARE( composer->messages().count(), 1 ); + kDebug() << composer->messages().first()->message()->encodedContent(); + } + + // unsupported CTE -> error. + { + Composer *composer = new Composer; + composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); + composer->infoPart()->setFrom( QString::fromLatin1( "me@me.me" ) ); + composer->infoPart()->setTo( QStringList( QString::fromLatin1( "you@you.you" ) ) ); + composer->textPart()->setWrappedPlainText( QString::fromLatin1( "sample content" ) ); + composer->textPart()->setOverrideTransferEncoding( Headers::CEbinary ); + QVERIFY( !composer->exec() ); + QCOMPARE( composer->error(), int( Job::BugError ) ); + kDebug() << composer->errorString(); + } + + // 8bit part when not EightBitTransport -> error. + { + Composer *composer = new Composer; + composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); + composer->infoPart()->setFrom( QString::fromLatin1( "me@me.me" ) ); + composer->infoPart()->setTo( QStringList( QString::fromLatin1( "you@you.you" ) ) ); + composer->textPart()->setWrappedPlainText( QString::fromLatin1( "sample content" ) ); + composer->textPart()->setOverrideTransferEncoding( Headers::CE8Bit ); + QVERIFY( !composer->exec() ); + QCOMPARE( composer->error(), int( Job::BugError ) ); + kDebug() << composer->errorString(); + } +} + +#include "composertest.moc" diff --git a/messagecomposer/tests/multipartjobtest.h b/messagecomposer/tests/composertest.h similarity index 87% copy from messagecomposer/tests/multipartjobtest.h copy to messagecomposer/tests/composertest.h index 98eed2d82..06af5ff14 100644 --- a/messagecomposer/tests/multipartjobtest.h +++ b/messagecomposer/tests/composertest.h @@ -1,32 +1,32 @@ /* 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 MULTIPARTJOBTEST_H -#define MULTIPARTJOBTEST_H +#ifndef COMPOSERTEST_H +#define COMPOSERTEST_H #include -class MultipartJobTest : public QObject +class ComposerTest : public QObject { Q_OBJECT private Q_SLOTS: - void testMultipartMixed(); + void testCTEErrors(); }; #endif diff --git a/messagecomposer/tests/contentjobtest.cpp b/messagecomposer/tests/contentjobtest.cpp index b953d52e5..63a469beb 100644 --- a/messagecomposer/tests/contentjobtest.cpp +++ b/messagecomposer/tests/contentjobtest.cpp @@ -1,105 +1,139 @@ /* 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 "contentjobtest.h" #include #include #include using namespace KMime; #include #include using namespace MessageComposer; QTEST_KDEMAIN( ContentJobTest, NoGUI ) void ContentJobTest::testContent() { Composer *composer = new Composer; ContentJob *cjob = new ContentJob( composer ); - QByteArray data( "birds came flying from the underground"); + QByteArray data( "birds came flying from the underground" ); cjob->setData( data ); QVERIFY( cjob->exec() ); Content *result = cjob->content(); result->assemble(); kDebug() << result->encodedContent(); QCOMPARE( result->body(), data ); QVERIFY( result->contentDisposition( false ) == 0 ); // Not created unless demanded. QVERIFY( result->contentType( false ) == 0 ); // Not created unless demanded. QVERIFY( result->contentTransferEncoding( false ) ); // KMime gives it a default one (7bit). } void ContentJobTest::testContentDisposition() { Composer *composer = new Composer; ContentJob *cjob = new ContentJob( composer ); - QByteArray data( "birds came flying from the underground"); + QByteArray data( "birds came flying from the underground" ); cjob->setData( data ); QString filename = QString::fromUtf8( "test_ăîşţâ.txt" ); cjob->contentDisposition()->setDisposition( Headers::CDattachment ); cjob->contentDisposition()->setFilename( filename ); QVERIFY( cjob->exec() ); Content *result = cjob->content(); result->assemble(); kDebug() << result->encodedContent(); QCOMPARE( result->body(), data ); QVERIFY( result->contentDisposition( false ) ); QCOMPARE( result->contentDisposition()->disposition(), Headers::CDattachment ); QCOMPARE( result->contentDisposition()->filename(), filename ); } void ContentJobTest::testContentType() { Composer *composer = new Composer; ContentJob *cjob = new ContentJob( composer ); - QByteArray data( "birds came flying from the underground"); + QByteArray data( "birds came flying from the underground" ); cjob->setData( data ); QByteArray mimeType( "text/plain" ); QByteArray charset( "utf-8" ); cjob->contentType()->setMimeType( mimeType ); cjob->contentType()->setCharset( charset ); QVERIFY( cjob->exec() ); Content *result = cjob->content(); result->assemble(); kDebug() << result->encodedContent(); QCOMPARE( result->body(), data ); QVERIFY( result->contentType( false ) ); QCOMPARE( result->contentType()->mimeType(), mimeType ); QCOMPARE( result->contentType()->charset(), charset ); } void ContentJobTest::testContentTransferEncoding() { Composer *composer = new Composer; - ContentJob *cjob = new ContentJob( composer ); - QByteArray data( "birds came flying from the underground"); - cjob->setData( data ); - cjob->contentTransferEncoding()->setEncoding( Headers::CEquPr ); - QVERIFY( cjob->exec() ); - Content *result = cjob->content(); - result->assemble(); - kDebug() << result->encodedContent(); - QCOMPARE( result->body(), data ); - QVERIFY( result->contentTransferEncoding( false ) ); - QCOMPARE( result->contentTransferEncoding()->encoding(), Headers::CEquPr ); + QVERIFY( !composer->behaviour().isActionEnabled( Behaviour::EightBitTransport ) ); + composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); + + // 7bit if possible. + { + ContentJob *cjob = new ContentJob( composer ); + QByteArray data( "and the sun will set for you..." ); + cjob->setData( data ); + QVERIFY( cjob->exec() ); + Content *result = cjob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + QVERIFY( result->contentTransferEncoding( false ) ); + QCOMPARE( result->contentTransferEncoding()->encoding(), Headers::CE7Bit ); + QCOMPARE( result->body(), data ); + } + + // quoted-printable if text doesn't fit in 7bit. + { + ContentJob *cjob = new ContentJob( composer ); + QByteArray data( "some long text to make qupr more compact than base64 [ăîşţâ]" ); // utf-8 + cjob->setData( data ); + QVERIFY( cjob->exec() ); + Content *result = cjob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + QVERIFY( result->contentTransferEncoding( false ) ); + QCOMPARE( result->contentTransferEncoding()->encoding(), Headers::CEquPr ); + QCOMPARE( result->body(), data ); + } + + // base64 if it's shorter than quoted-printable + { + ContentJob *cjob = new ContentJob( composer ); + QByteArray data( "[ăîşţâ]" ); // utf-8 + cjob->setData( data ); + QVERIFY( cjob->exec() ); + QVERIFY( cjob->exec() ); + Content *result = cjob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + QVERIFY( result->contentTransferEncoding( false ) ); + QCOMPARE( result->contentTransferEncoding()->encoding(), Headers::CEbase64 ); + QCOMPARE( result->body(), data ); + } } #include "contentjobtest.moc" diff --git a/messagecomposer/tests/maintextjobtest.cpp b/messagecomposer/tests/maintextjobtest.cpp index 5af866d01..e6c30cd3e 100644 --- a/messagecomposer/tests/maintextjobtest.cpp +++ b/messagecomposer/tests/maintextjobtest.cpp @@ -1,156 +1,184 @@ /* 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 "maintextjobtest.h" #include #include #include #include using namespace KMime; #include #include #include using namespace MessageComposer; QTEST_KDEMAIN( MainTextJobTest, NoGUI ) void MainTextJobTest::testPlainText() { Composer *composer = new Composer; composer->behaviour().disableAction( Behaviour::UseGui ); TextPart *textPart = new TextPart; QString data = QString::fromLatin1( "they said their nevers they slept their dream" ); QList charsets; charsets << "us-ascii" << "utf-8"; textPart->setWrappedPlainText( data ); textPart->setCharsets( charsets ); MainTextJob *mjob = new MainTextJob( textPart, composer ); QVERIFY( mjob->exec() ); Content *result = mjob->content(); result->assemble(); kDebug() << result->encodedContent(); QVERIFY( result->contentType( false ) ); QCOMPARE( result->contentType()->mimeType(), QByteArray( "text/plain" ) ); QCOMPARE( result->contentType()->charset(), QByteArray( "us-ascii" ) ); QCOMPARE( QString::fromLatin1( result->body() ), data ); } void MainTextJobTest::testWrappingErrors() { { Composer *composer = new Composer; composer->behaviour().disableAction( Behaviour::UseGui ); composer->behaviour().disableAction( Behaviour::UseWrapping ); composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); TextPart *textPart = new TextPart; QString data = QString::fromLatin1( "they said their nevers they slept their dream" ); textPart->setWrappedPlainText( data ); MainTextJob *mjob = new MainTextJob( textPart, composer ); QVERIFY( !mjob->exec() ); // error: not UseWrapping but given only wrapped text QCOMPARE( mjob->error(), int( Job::BugError ) ); } { Composer *composer = new Composer; composer->behaviour().disableAction( Behaviour::UseGui ); composer->behaviour().enableAction( Behaviour::UseWrapping ); composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); TextPart *textPart = new TextPart; QString data = QString::fromLatin1( "they said their nevers they slept their dream" ); textPart->setCleanPlainText( data ); MainTextJob *mjob = new MainTextJob( textPart, composer ); QVERIFY( !mjob->exec() ); // error: UseWrapping but given only clean text QCOMPARE( mjob->error(), int( Job::BugError ) ); } } void MainTextJobTest::testCustomCharset() { Composer *composer = new Composer; composer->behaviour().disableAction( Behaviour::UseGui ); TextPart *textPart = new TextPart; QString data = QString::fromUtf8( "şi el o să se-nchidă cu o frunză de pelin" ); QByteArray charset( "iso-8859-2" ); textPart->setWrappedPlainText( data ); textPart->setCharsets( QList() << charset ); MainTextJob *mjob = new MainTextJob( textPart, composer ); QVERIFY( mjob->exec() ); Content *result = mjob->content(); result->assemble(); kDebug() << result->encodedContent(); QVERIFY( result->contentType( false ) ); QCOMPARE( result->contentType()->mimeType(), QByteArray( "text/plain" ) ); QCOMPARE( result->contentType()->charset(), charset ); QByteArray outData = result->body(); QTextCodec *codec = QTextCodec::codecForName( charset ); QVERIFY( codec ); QCOMPARE( codec->toUnicode( outData ), data ); } void MainTextJobTest::testNoCharset() { Composer *composer = new Composer; composer->behaviour().disableAction( Behaviour::UseGui ); TextPart *textPart = new TextPart; QString data = QString::fromLatin1( "do you still play the accordion?" ); textPart->setWrappedPlainText( data ); MainTextJob *mjob = new MainTextJob( textPart, composer ); QVERIFY( !mjob->exec() ); // Error. QCOMPARE( mjob->error(), int( Job::BugError ) ); kDebug() << mjob->errorString(); } void MainTextJobTest::testBadCharset() { Composer *composer = new Composer; composer->behaviour().disableAction( Behaviour::UseGui ); TextPart *textPart = new TextPart; QString data = QString::fromUtf8( "el a plâns peste ţară cu lacrima limbii noastre" ); QByteArray charset( "us-ascii" ); // Cannot handle Romanian chars. textPart->setWrappedPlainText( data ); textPart->setCharsets( QList() << charset ); MainTextJob *mjob = new MainTextJob( textPart, composer ); QVERIFY( !mjob->exec() ); // Error. QCOMPARE( mjob->error(), int( Job::UserError ) ); kDebug() << mjob->errorString(); } void MainTextJobTest::testFallbackCharset() { Composer *composer = new Composer; composer->behaviour().disableAction( Behaviour::UseGui ); composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); TextPart *textPart = new TextPart; QString data = QString::fromLatin1( "and when he falleth..." ); textPart->setWrappedPlainText( data ); MainTextJob *mjob = new MainTextJob( textPart, composer ); QVERIFY( mjob->exec() ); Content *result = mjob->content(); result->assemble(); kDebug() << result->encodedContent(); QVERIFY( result->contentType( false ) ); QCOMPARE( result->contentType()->mimeType(), QByteArray( "text/plain" ) ); QCOMPARE( result->contentType()->charset(), QByteArray( "utf-8" ) ); // Fallback is UTF-8. QCOMPARE( QString::fromLatin1( result->body() ), data ); } +void MainTextJobTest::testOverrideCTE() +{ + Composer *composer = new Composer; + QVERIFY( !composer->behaviour().isActionEnabled( Behaviour::EightBitTransport ) ); + composer->behaviour().enableAction( Behaviour::UseFallbackCharset ); + TextPart *textPart = new TextPart; + + // 8bit if asked for and allowed. + { + composer->behaviour().enableAction( Behaviour::EightBitTransport ); + QString data = QString::fromUtf8( "[ăîşţâ]" ); + textPart->setWrappedPlainText( data ); + // Force it to use an 8bit encoding: + QByteArray charset( "iso-8859-2" ); + textPart->setCharsets( QList() << charset ); + MainTextJob *mjob = new MainTextJob( textPart, composer ); + QVERIFY( mjob->exec() ); + Content *result = mjob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + QVERIFY( result->contentTransferEncoding( false ) ); + QCOMPARE( result->contentTransferEncoding()->encoding(), Headers::CE8Bit ); + QTextCodec *codec = QTextCodec::codecForName( charset ); + QVERIFY( codec ); + QCOMPARE( codec->toUnicode( result->body() ), data ); + } +} + #include "maintextjobtest.moc" diff --git a/messagecomposer/tests/maintextjobtest.h b/messagecomposer/tests/maintextjobtest.h index eb37e76a7..ddcab2364 100644 --- a/messagecomposer/tests/maintextjobtest.h +++ b/messagecomposer/tests/maintextjobtest.h @@ -1,40 +1,43 @@ /* 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 MAINTEXTJOBTEST_H #define MAINTEXTJOBTEST_H #include class MainTextJobTest : public QObject { Q_OBJECT private Q_SLOTS: // "text/plain" tests: void testPlainText(); void testWrappingErrors(); // charset tests: void testCustomCharset(); void testNoCharset(); void testBadCharset(); void testFallbackCharset(); + + // Content-Transfer-Encoding tests: + void testOverrideCTE(); }; #endif diff --git a/messagecomposer/tests/multipartjobtest.cpp b/messagecomposer/tests/multipartjobtest.cpp index 1622c3e57..947727a96 100644 --- a/messagecomposer/tests/multipartjobtest.cpp +++ b/messagecomposer/tests/multipartjobtest.cpp @@ -1,82 +1,104 @@ /* 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 "multipartjobtest.h" #include #include #include using namespace KMime; #include #include #include using namespace MessageComposer; QTEST_KDEMAIN( MultipartJobTest, NoGUI ) void MultipartJobTest::testMultipartMixed() { Composer *composer = new Composer; MultipartJob *mjob = new MultipartJob( composer ); mjob->setMultipartSubtype( "mixed" ); QByteArray data1( "one" ); QByteArray data2( "two" ); QByteArray type1( "text/plain" ); QByteArray type2( "application/x-mors-ontologica" ); { ContentJob *cjob = new ContentJob( mjob ); cjob->setData( data1 ); cjob->contentType()->setMimeType( type1 ); } { ContentJob *cjob = new ContentJob( mjob ); cjob->setData( data2 ); cjob->contentType()->setMimeType( type2 ); } QVERIFY( mjob->exec() ); Content *result = mjob->content(); result->assemble(); kDebug() << result->encodedContent(); QVERIFY( result->contentType( false ) ); QCOMPARE( result->contentType()->mimeType(), QByteArray( "multipart/mixed" ) ); QCOMPARE( result->contents().count(), 2 ); { Content *c = result->contents().at( 0 ); QCOMPARE( c->body(), data1 ); QVERIFY( c->contentType( false ) ); QCOMPARE( c->contentType()->mimeType(), type1 ); } { Content *c = result->contents().at( 1 ); QCOMPARE( c->body(), data2 ); QVERIFY( c->contentType( false ) ); QCOMPARE( c->contentType()->mimeType(), type2 ); } } +void MultipartJobTest::test8BitPropagation() +{ + // If a subpart is 8bit, its parent must be 8bit too. + + Composer *composer = new Composer; + composer->behaviour().enableAction( Behaviour::EightBitTransport ); + MultipartJob *mjob = new MultipartJob( composer ); + mjob->setMultipartSubtype( "mixed" ); + MultipartJob *mjob2 = new MultipartJob( mjob ); + mjob2->setMultipartSubtype( "mixed" ); + ContentJob *cjob = new ContentJob( mjob2 ); + QByteArray data( "time is so short and I'm sure there must be something more" ); + cjob->setData( data ); + cjob->contentTransferEncoding()->setEncoding( Headers::CE8Bit ); + QVERIFY( mjob->exec() ); + Content *content = mjob->content(); + content->assemble(); + kDebug() << content->encodedContent(); + QVERIFY( content->contentTransferEncoding( false ) ); + QCOMPARE( content->contentTransferEncoding()->encoding(), Headers::CE8Bit ); +} + #include "multipartjobtest.moc" diff --git a/messagecomposer/tests/multipartjobtest.h b/messagecomposer/tests/multipartjobtest.h index 98eed2d82..03f3996e9 100644 --- a/messagecomposer/tests/multipartjobtest.h +++ b/messagecomposer/tests/multipartjobtest.h @@ -1,32 +1,33 @@ /* 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 MULTIPARTJOBTEST_H #define MULTIPARTJOBTEST_H #include class MultipartJobTest : public QObject { Q_OBJECT private Q_SLOTS: void testMultipartMixed(); + void test8BitPropagation(); }; #endif diff --git a/messagecomposer/textpart.h b/messagecomposer/textpart.h index 919c36cdf..09c5e7d5a 100644 --- a/messagecomposer/textpart.h +++ b/messagecomposer/textpart.h @@ -1,57 +1,59 @@ /* 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 MESSAGECOMPOSER_TEXTPART_H #define MESSAGECOMPOSER_TEXTPART_H #include #include "messagecomposer_export.h" #include "messagepart.h" namespace MessageComposer { +/** setOverrideTransferEncoding for a textPart means setting it for the plain text part, + and possibly for the html part. */ class MESSAGECOMPOSER_EXPORT TextPart : public MessagePart { Q_OBJECT public: explicit TextPart( QObject *parent = 0 ); virtual ~TextPart(); QList charsets() const; void setCharsets( const QList &charsets ); QString cleanPlainText() const; void setCleanPlainText( const QString &text ); QString wrappedPlainText() const; void setWrappedPlainText( const QString &text ); bool isHtmlUsed() const; QString cleanHtml() const; void setCleanHtml( const QString &text ); private: class Private; Private *const d; }; } // namespace MessageComposer #endif // MESSAGECOMPOSER_TEXTPART_H diff --git a/messagecomposer/messagepart.cpp b/messagecomposer/util.cpp similarity index 59% copy from messagecomposer/messagepart.cpp copy to messagecomposer/util.cpp index d69a811a0..41d5fd713 100644 --- a/messagecomposer/messagepart.cpp +++ b/messagecomposer/util.cpp @@ -1,44 +1,37 @@ /* 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 "messagepart.h" - -#include +#include "util.h" +using namespace KMime; using namespace MessageComposer; -class MessagePart::Private -{ - public: -}; - - - -MessagePart::MessagePart( QObject *parent ) - : QObject( parent ) - , d( new Private ) -{ -} - -MessagePart::~MessagePart() +QString MessageComposer::nameForEncoding( Headers::contentEncoding enc ) { - delete d; + switch( enc ) { + case Headers::CE7Bit: return QString::fromLatin1( "7bit" ); + case Headers::CE8Bit: return QString::fromLatin1( "8bit" ); + case Headers::CEquPr: return QString::fromLatin1( "quoted-printable" ); + case Headers::CEbase64: return QString::fromLatin1( "base64" ); + case Headers::CEuuenc: return QString::fromLatin1( "uuencode" ); + case Headers::CEbinary: return QString::fromLatin1( "binary" ); + default: return QString::fromLatin1( "unknown" ); + } } -#include "messagepart.moc" diff --git a/messagecomposer/tests/multipartjobtest.h b/messagecomposer/util.h similarity index 71% copy from messagecomposer/tests/multipartjobtest.h copy to messagecomposer/util.h index 98eed2d82..d966e1bd1 100644 --- a/messagecomposer/tests/multipartjobtest.h +++ b/messagecomposer/util.h @@ -1,32 +1,37 @@ /* 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 MULTIPARTJOBTEST_H -#define MULTIPARTJOBTEST_H +#ifndef MESSAGECOMPOSER_UTIL_H +#define MESSAGECOMPOSER_UTIL_H -#include +//#include "messagecomposer_export.h" -class MultipartJobTest : public QObject -{ - Q_OBJECT - private Q_SLOTS: - void testMultipartMixed(); -}; +#include + +#include + +namespace MessageComposer { + +// TODO move to KMime? +// or move to ContentJob if we'll only need it there. +QString nameForEncoding( KMime::Headers::contentEncoding enc ); + +} #endif