diff --git a/kmime/kmime_message.cpp b/kmime/kmime_message.cpp index 43b70d96b..8c7e840c2 100644 --- a/kmime/kmime_message.cpp +++ b/kmime/kmime_message.cpp @@ -1,269 +1,275 @@ /* kmime_message.cpp KMime, the KDE internet mail/usenet news message library. Copyright (c) 2001 the KMime authors. See file AUTHORS for details This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmime_message.h" #include "kmime_message_p.h" #include "kmime_util_p.h" using namespace KMime; namespace KMime { Message::Message() : Content( new MessagePrivate( this ) ) {} Message::Message(MessagePrivate * d) : Content( d ) {} Message::~Message() {} void Message::parse() { Q_D(Message); Content::parse(); QByteArray raw; if ( !( raw = rawHeader( d->subject.type() ) ).isEmpty() ) d->subject.from7BitString( raw ); if ( !( raw = rawHeader( d->date.type() ) ).isEmpty() ) d->date.from7BitString( raw ); } QByteArray Message::assembleHeaders() { Q_D(Message); Headers::Base *h; QByteArray newHead; //Message-ID if ( ( h = messageID( false ) ) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } //From h = from(); // "From" is mandatory - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); + if ( !h->isEmpty() ) { + newHead += h->as7BitString() + '\n'; + KMime::removeHeader( d->head, h->type() ); + } //Subject h = subject(); // "Subject" is mandatory - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); + if ( !h->isEmpty() ) { + newHead += h->as7BitString() + '\n'; + KMime::removeHeader( d->head, h->type() ); + } //To if ( ( h = to( false )) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } //Cc if ( ( h = cc( false )) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } //Reply-To if ( ( h = replyTo( false )) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } //Date h = date(); // "Date" is mandatory - newHead += h->as7BitString() + '\n'; - KMime::removeHeader( d->head, h->type() ); + if ( !h->isEmpty() ) { + newHead += h->as7BitString() + '\n'; + KMime::removeHeader( d->head, h->type() ); + } //References if ( ( h = references( false )) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } //Organization if ( ( h = organization( false )) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } //UserAgent if ( ( h = userAgent( false )) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } // In-Reply-To if ( ( h = inReplyTo( false ) ) != 0 && !h->isEmpty() ) { newHead += h->as7BitString() + '\n'; KMime::removeHeader( d->head, h->type() ); } //Mime-Version newHead += "MIME-Version: 1.0\n"; KMime::removeHeader( d->head, "MIME-Version" ); return newHead + Content::assembleHeaders(); } void Message::clear() { Q_D(Message); d->subject.clear(); d->date.clear(); Content::clear(); } Headers::Base *Message::getHeaderByType( const char *type ) { Q_D(Message); if ( strcasecmp( "Subject", type ) == 0 ) { if ( d->subject.isEmpty() ) { return 0; } else { return &d->subject; } } else if ( strcasecmp("Date", type ) == 0 ){ if ( d->date.isEmpty() ) { return 0; } else { return &d->date; } } else { return Content::getHeaderByType( type ); } } void Message::setHeader( Headers::Base *h ) { Q_D(Message); bool del = true; if ( h->is( "Subject" ) ) { d->subject.fromUnicodeString( h->asUnicodeString(), h->rfc2047Charset() ); } else if ( h->is( "Date" ) ) { d->date.setDateTime( (static_cast( h))->dateTime() ); } else { del = false; Content::setHeader( h ); } if ( del ) delete h; } bool Message::removeHeader( const char *type ) { Q_D(Message); if ( strcasecmp( "Subject", type ) == 0 ) { d->subject.clear(); } else if ( strcasecmp( "Date", type ) == 0 ) { d->date.clear(); } else { return Content::removeHeader( type ); } return true; } Headers::Subject *Message::subject( bool create ) { Q_D( Message ); if ( !create && d->subject.isEmpty() ) { return 0; } return &d->subject; } Headers::Date *Message::date( bool create ) { Q_D( Message ); if ( !create && d->date.isEmpty() ) { return 0; } return &d->date; } bool Message::isTopLevel() const { return true; } Content *Message::mainBodyPart( const QByteArray &type ) { KMime::Content *c = this; while ( c ) { // not a multipart message if ( !c->contentType()->isMultipart() ) { if ( c->contentType()->mimeType() == type || type.isEmpty() ) { return c; } return 0; } // empty multipart if ( c->contents().count() == 0 ) { return 0; } // multipart/alternative if ( c->contentType()->subType() == "alternative" ) { if ( type.isEmpty() ) { return c->contents().first(); } foreach ( Content *c1, c->contents() ) { if ( c1->contentType()->mimeType() == type ) { return c1; } } return 0; } c = c->contents().first(); } return 0; } // @cond PRIVATE #define kmime_mk_header_accessor( header, method ) \ Headers::header *Message::method( bool create ) { \ Headers::header *p = 0; \ return getHeaderInstance( p, create ); \ } kmime_mk_header_accessor( MessageID, messageID ) kmime_mk_header_accessor( Organization, organization ) kmime_mk_header_accessor( From, from ) kmime_mk_header_accessor( ReplyTo, replyTo ) kmime_mk_header_accessor( To, to ) kmime_mk_header_accessor( Cc, cc ) kmime_mk_header_accessor( Bcc, bcc ) kmime_mk_header_accessor( References, references ) kmime_mk_header_accessor( UserAgent, userAgent ) kmime_mk_header_accessor( InReplyTo, inReplyTo ) kmime_mk_header_accessor( Sender, sender ) #undef kmime_mk_header_accessor // @endcond } diff --git a/kmime/tests/kmime_message_test.cpp b/kmime/tests/kmime_message_test.cpp index 921e19263..60cf84976 100644 --- a/kmime/tests/kmime_message_test.cpp +++ b/kmime/tests/kmime_message_test.cpp @@ -1,145 +1,174 @@ /* 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 ); // 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; + qDebug() << msg.encodedContent(); + msg2.setContent( msg.encodedContent() ); + msg2.parse(); + msg2.assemble(); + + QCOMPARE( body, QString::fromAscii( msg2.body() ) ); +} + diff --git a/kmime/tests/kmime_message_test.h b/kmime/tests/kmime_message_test.h index 942a7b183..ad00c16a7 100644 --- a/kmime/tests/kmime_message_test.h +++ b/kmime/tests/kmime_message_test.h @@ -1,35 +1,36 @@ /* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_MESSAGE_TEST_H #define KMIME_MESSAGE_TEST_H #include class MessageTest : public QObject { Q_OBJECT private slots: void testMainBodyPart(); void testBrunosMultiAssembleBug(); void testWillsAndTillsCrash(); + void missingHeadersTest(); }; #endif