diff --git a/messagecomposer/job/signjob.cpp b/messagecomposer/job/signjob.cpp index a8f2be0ce9..b74c1459ab 100644 --- a/messagecomposer/job/signjob.cpp +++ b/messagecomposer/job/signjob.cpp @@ -1,253 +1,253 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi 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 "signjob.h" #include "contentjobbase_p.h" #include "kleo/cryptobackendfactory.h" #include "kleo/cryptobackend.h" #include "kleo/enum.h" #include "kleo/signjob.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include using namespace MessageComposer; class MessageComposer::SignJobPrivate : public ContentJobBasePrivate { public: SignJobPrivate( SignJob *qq ) : ContentJobBasePrivate( qq ) , content( 0 ) { } KMime::Content* content; std::vector signers; Kleo::CryptoMessageFormat format; // copied from messagecomposer.cpp bool binaryHint( Kleo::CryptoMessageFormat f ) { switch ( f ) { case Kleo::SMIMEFormat: case Kleo::SMIMEOpaqueFormat: return true; default: case Kleo::OpenPGPMIMEFormat: case Kleo::InlineOpenPGPFormat: return false; } } GpgME::SignatureMode signingMode( Kleo::CryptoMessageFormat f ) { switch ( f ) { case Kleo::SMIMEOpaqueFormat: return GpgME::NormalSignatureMode; case Kleo::InlineOpenPGPFormat: return GpgME::Clearsigned; default: case Kleo::SMIMEFormat: case Kleo::OpenPGPMIMEFormat: return GpgME::Detached; } } Q_DECLARE_PUBLIC( SignJob ) }; SignJob::SignJob( QObject *parent ) : ContentJobBase( *new SignJobPrivate( this ), parent ) { } SignJob::~SignJob() { } void SignJob::setContent( KMime::Content* content ) { Q_D( SignJob ); d->content = content; } void SignJob::setCryptoMessageFormat( Kleo::CryptoMessageFormat format) { Q_D( SignJob ); // There *must* be a concrete format set at this point. Q_ASSERT( format == Kleo::OpenPGPMIMEFormat || format == Kleo::InlineOpenPGPFormat || format == Kleo::SMIMEFormat || format == Kleo::SMIMEOpaqueFormat ); d->format = format; } void SignJob::setSigningKeys( std::vector& signers ) { Q_D( SignJob ); d->signers = signers; } KMime::Content* SignJob::origContent() { Q_D( SignJob ); return d->content; } void SignJob::process() { Q_D( SignJob ); Q_ASSERT( d->resultContent == 0 ); // Not processed before. // if setContent hasn't been called, we assume that a subjob was added // and we want to use that if( !d->content ) { Q_ASSERT( d->subjobContents.size() == 1 ); d->content = d->subjobContents.first(); } //d->resultContent = new KMime::Content; const Kleo::CryptoBackend::Protocol *proto = 0; if( d->format & Kleo::AnyOpenPGP ) { proto = Kleo::CryptoBackendFactory::instance()->openpgp(); } else if( d->format & Kleo::AnySMIME ) { proto = Kleo::CryptoBackendFactory::instance()->smime(); } Q_ASSERT( proto ); kDebug() << "creating signJob from:" << proto->name() << proto->displayName(); std::auto_ptr job( proto->signJob( !d->binaryHint( d->format ), d->format == Kleo::InlineOpenPGPFormat ) ); // for now just do the main recipients - QByteArray signature; d->content->assemble(); // replace simple LFs by CRLFs for all MIME supporting CryptPlugs // according to RfC 2633, 3.1.1 Canonicalization QByteArray content; if( d->format & Kleo::InlineOpenPGPFormat ) { content = d->content->body(); } else if( !( d->format & Kleo::SMIMEOpaqueFormat ) ) { // replace "From " and "--" at the beginning of lines // with encoded versions according to RfC 3156, 3 // Note: If any line begins with the string "From ", it is strongly // suggested that either the Quoted-Printable or Base64 MIME encoding // be applied. - if (d->content->contentTransferEncoding()->encoding() == KMime::Headers::CEquPr || - d->content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit ) { + if ((d->content->contentTransferEncoding()->encoding() == KMime::Headers::CEquPr || + d->content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) && !d->content->contentType(false)) { QByteArray body = d->content->encodedBody(); bool changed = false; QList search; QList replacements; search << "From " << "from " << "-"; replacements << "From=20" << "from=20" << "=2D"; if ( d->content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit ) { for ( int i = 0; i < search.size(); ++i ) { QByteArray start = "\n" % search[i]; if ( body.indexOf( start ) > -1 || body.startsWith( search[i] ) ){ changed = true; break; } } if ( changed ) { d->content->contentTransferEncoding()->setEncoding( KMime::Headers::CEquPr ); d->content->assemble(); body = d->content->encodedBody(); } } for ( int i = 0; i < search.size(); ++i ) { QByteArray start = "\n" % search[i]; QByteArray replace = "\n" % replacements[i]; if ( body.indexOf( start ) > -1 ){ changed = true; body.replace( start, replace ); } if ( body.startsWith( search[i] ) ) { changed = true; body.replace( 0, search[i].size(), replacements[i] ); } } if ( changed ) { kDebug() << "Content changed"; d->content->setBody( body ); d->content->contentTransferEncoding()->setDecoded( false ); } } content = KMime::LFtoCRLF( d->content->encodedContent() ); } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged content = d->content->encodedContent(); } // FIXME: Make this async + QByteArray signature; GpgME::SigningResult res = job->exec( d->signers, content, d->signingMode( d->format ), signature ); // exec'ed jobs don't delete themselves job->deleteLater(); if ( res.error() ) { kDebug() << "signing failed:" << res.error().asString(); // job->showErrorDialog( globalPart()->parentWidgetForGui() ); setError( res.error().code() ); setErrorText( QString::fromLocal8Bit( res.error().asString() ) ); } else { QByteArray signatureHashAlgo = res.createdSignature( 0 ).hashAlgorithmAsString(); d->resultContent = MessageComposer::Util::composeHeadersAndBody( d->content, signature, d->format, true, signatureHashAlgo ); } emitResult(); } diff --git a/messagecomposer/tests/signjobtest.cpp b/messagecomposer/tests/signjobtest.cpp index 20c38dcdd8..e952cbe289 100644 --- a/messagecomposer/tests/signjobtest.cpp +++ b/messagecomposer/tests/signjobtest.cpp @@ -1,176 +1,224 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi 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 "signjobtest.h" #include #include #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include #include #include #include #include #include #include #include #include #include #include QTEST_KDEMAIN( SignJobTest, GUI ) void SignJobTest::initTestCase() { MessageCore::Test::setupEnv(); } void SignJobTest::testContentDirect() { std::vector< GpgME::Key > keys = MessageCore::Test::getKeys(); MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob* sJob = new MessageComposer::SignJob( composer ); QVERIFY( composer ); QVERIFY( sJob ); QByteArray data( QString::fromLocal8Bit( "one flew over the cuckoo's nest" ).toUtf8() ); KMime::Content* content = new KMime::Content; content->setBody( data ); sJob->setContent( content ); sJob->setCryptoMessageFormat( Kleo::OpenPGPMIMEFormat ); sJob->setSigningKeys( keys ); checkSignJob( sJob ); } void SignJobTest::testContentChained() { std::vector< GpgME::Key > keys = MessageCore::Test::getKeys(); QByteArray data( QString::fromLocal8Bit( "one flew over the cuckoo's nest" ).toUtf8() ); KMime::Content* content = new KMime::Content; content->setBody( data ); MessageComposer::TransparentJob* tJob = new MessageComposer::TransparentJob; tJob->setContent( content ); MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob* sJob = new MessageComposer::SignJob( composer ); sJob->setCryptoMessageFormat( Kleo::OpenPGPMIMEFormat ); sJob->setSigningKeys( keys ); sJob->appendSubjob( tJob ); checkSignJob( sJob ); } void SignJobTest::testHeaders() { std::vector< GpgME::Key > keys = MessageCore::Test::getKeys(); MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob* sJob = new MessageComposer::SignJob( composer ); QVERIFY( composer ); QVERIFY( sJob ); QByteArray data( QString::fromLocal8Bit( "one flew over the cuckoo's nest" ).toUtf8() ); KMime::Content* content = new KMime::Content; content->setBody( data ); sJob->setContent( content ); sJob->setCryptoMessageFormat( Kleo::OpenPGPMIMEFormat ); sJob->setSigningKeys( keys ); VERIFYEXEC( sJob ); QByteArray mimeType( "multipart/signed" ); QByteArray charset( "ISO-8859-1" ); KMime::Content *result = sJob->content(); result->assemble(); kDebug() << result->encodedContent(); QVERIFY( result->contentType( false ) ); QCOMPARE( result->contentType()->mimeType(), mimeType ); QCOMPARE( result->contentType()->charset(), charset ); QCOMPARE( result->contentType()->parameter( QString::fromLocal8Bit( "micalg" ) ), QString::fromLocal8Bit( "pgp-sha256" ) ); QCOMPARE( result->contentType()->parameter( QString::fromLocal8Bit( "protocol" ) ), QString::fromLocal8Bit( "application/pgp-signature" ) ); QCOMPARE( result->contentTransferEncoding()->encoding(), KMime::Headers::CE7Bit ); } void SignJobTest::testRecommentationRFC3156() { std::vector< GpgME::Key > keys = MessageCore::Test::getKeys(); QString data = QString::fromUtf8( "=2D Magic foo\nFrom test\n\n-- quaak\nOhno"); KMime::Headers::contentEncoding cte = KMime::Headers::CEquPr; MessageComposer::Composer *composer = new MessageComposer::Composer; MessageComposer::SignJob* sJob = new MessageComposer::SignJob( composer ); QVERIFY( composer ); QVERIFY( sJob ); KMime::Content* content = new KMime::Content; content->setBody( data.toUtf8() ); sJob->setContent( content ); sJob->setCryptoMessageFormat( Kleo::OpenPGPMIMEFormat ); sJob->setSigningKeys( keys ); VERIFYEXEC( sJob ); KMime::Content *result = sJob->content(); result->assemble(); kDebug() << result->encodedContent(); QByteArray body = MessageCore::NodeHelper::firstChild( result )->body(); QCOMPARE( QString::fromUtf8( body ), QString::fromUtf8( "=3D2D Magic foo\nFrom=20test\n\n=2D- quaak\nOhno" ) ); ComposerTestUtil::verify( true, false, result, data.toUtf8(), Kleo::OpenPGPMIMEFormat, cte ); +} + +void SignJobTest::testMixedContent() +{ + std::vector< GpgME::Key > keys = MessageCore::Test::getKeys(); + + QString data = QString::fromUtf8( "=2D Magic foo\nFrom test\n\n-- quaak\nOhno"); + + MessageComposer::Composer *composer = new MessageComposer::Composer; + MessageComposer::SignJob* sJob = new MessageComposer::SignJob( composer ); + + QVERIFY( composer ); + QVERIFY( sJob ); + + KMime::Content* content = new KMime::Content; + content->contentType()->setMimeType(QByteArray("multipart/mixed")); + content->contentType()->setBoundary( KMime::multiPartBoundary() ); + KMime::Content* subcontent = new KMime::Content; + subcontent->contentType()->setMimeType(QByteArray("text/plain")); + subcontent->setBody( data.toUtf8() ); + KMime::Content* attachment = new KMime::Content; + attachment->contentType()->setMimeType(QByteArray("text/plain")); + QByteArray attachmentData("an attachment"); + attachment->setBody(attachmentData); + + content->addContent(subcontent); + content->addContent(attachment); + content->assemble(); + + sJob->setContent( content ); + sJob->setCryptoMessageFormat( Kleo::OpenPGPMIMEFormat ); + sJob->setSigningKeys( keys ); + + VERIFYEXEC( sJob ); + + KMime::Content *result = sJob->content(); + result->assemble(); + kDebug() << result->encodedContent(); + + KMime::Content* firstChild = MessageCore::NodeHelper::firstChild(result); + QCOMPARE(result->contents().count(), 2 ); + QCOMPARE(firstChild->contents().count(), 2 ); + QCOMPARE(firstChild->body(), QByteArray()); + QCOMPARE(firstChild->contentType()->mimeType(), QByteArray( "multipart/mixed" ) ); + QCOMPARE(firstChild->contents()[0]->body(), data.toUtf8()); + QCOMPARE(firstChild->contents()[1]->body(), attachmentData); + ComposerTestUtil::verify(true, false, result, data.toUtf8(), + Kleo::OpenPGPMIMEFormat, KMime::Headers::CE7Bit); } void SignJobTest::checkSignJob( MessageComposer::SignJob* sJob ) { VERIFYEXEC( sJob ); KMime::Content* result = sJob->content(); Q_ASSERT( result ); result->assemble(); ComposerTestUtil::verifySignature( result, QString::fromLocal8Bit( "one flew over the cuckoo's nest" ).toUtf8(), Kleo::OpenPGPMIMEFormat, KMime::Headers::CE7Bit ); } diff --git a/messagecomposer/tests/signjobtest.h b/messagecomposer/tests/signjobtest.h index d93bd1387d..4ca5658b11 100644 --- a/messagecomposer/tests/signjobtest.h +++ b/messagecomposer/tests/signjobtest.h @@ -1,53 +1,54 @@ /* Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Leo Franchi 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 SIGNJOBJOBTEST_H #define SIGNJOBJOBTEST_H #include #include #include class KJob; namespace MessageComposer { class SignJob; } class SignJobTest : public QObject { Q_OBJECT public slots: void initTestCase(); private Q_SLOTS: void testContentDirect(); void testContentChained(); void testHeaders(); void testRecommentationRFC3156(); + void testMixedContent(); private: void checkSignJob( MessageComposer::SignJob* sJob ); }; #endif