1/*
2 Copyright (c) 2007 Till Adam <adam@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20#include "akonadi_serializer_mail.h"
21
22#include <QtCore/qplugin.h>
23
24#include <kdebug.h>
25#include <kmime/kmime_message.h>
26#include <boost/shared_ptr.hpp>
27
28#include <akonadi/item.h>
29#include <akonadi/kmime/messageparts.h>
30#include <akonadi/private/imapparser_p.h>
31
32using namespace Akonadi;
33using namespace KMime;
34
35QString StringPool::sharedValue( const QString &value )
36{
37 QMutexLocker lock(&m_mutex);
38 QSet<QString>::const_iterator it = m_pool.constFind(value);
39 if ( it != m_pool.constEnd() )
40 return *it;
41 m_pool.insert(value);
42 return value;
43}
44
45template <typename T> static void parseAddrList( const QVarLengthArray<QByteArray, 16> &addrList, T *hdr,
46 int version, StringPool& pool )
47{
48 hdr->clear();
49 const int count = addrList.count();
50 QVarLengthArray<QByteArray, 16> addr;
51 for ( int i = 0; i < count; ++i ) {
52 ImapParser::parseParenthesizedList( addrList[ i ], addr );
53 if ( addr.count() != 4 ) {
54 kWarning( 5264 ) << "Error parsing envelope address field: " << addrList[ i ];
55 continue;
56 }
57 KMime::Types::Mailbox addrField;
58 if ( version == 0 )
59 addrField.setNameFrom7Bit( addr[0] );
60 else if ( version == 1 )
61 addrField.setName( pool.sharedValue( QString::fromUtf8( addr[0] ) ) );
62 KMime::Types::AddrSpec addrSpec;
63 addrSpec.localPart = pool.sharedValue( QString::fromUtf8( addr[2] ) );
64 addrSpec.domain = pool.sharedValue( QString::fromUtf8( addr[3] ) );
65 addrField.setAddress( addrSpec );
66 hdr->addAddress( addrField );
67 }
68}
69
70
71bool SerializerPluginMail::deserialize( Item& item, const QByteArray& label, QIODevice& data, int version )
72{
73 if ( label != MessagePart::Body && label != MessagePart::Envelope && label != MessagePart::Header )
74 return false;
75
76 KMime::Message::Ptr msg;
77 if ( !item.hasPayload() ) {
78 Message *m = new Message();
79 msg = KMime::Message::Ptr( m );
80 item.setPayload( msg );
81 } else {
82 msg = item.payload<KMime::Message::Ptr>();
83 }
84
85 QByteArray buffer = data.readAll();
86 if ( buffer.isEmpty() )
87 return true;
88 if ( label == MessagePart::Body ) {
89 msg->setContent( buffer );
90 msg->parse();
91 } else if ( label == MessagePart::Header ) {
92 if ( msg->body().isEmpty() && msg->contents().isEmpty() ) {
93 msg->setHead( buffer );
94 msg->parse();
95 }
96 } else if ( label == MessagePart::Envelope ) {
97 QVarLengthArray<QByteArray, 16> env;
98 ImapParser::parseParenthesizedList( buffer, env );
99 if ( env.count() < 10 ) {
100 kWarning( 5264 ) << "Akonadi KMime Deserializer: Got invalid envelope: " << buffer;
101 return false;
102 }
103 Q_ASSERT( env.count() >= 10 );
104 // date
105 msg->date()->from7BitString( env[0] );
106 // subject
107 msg->subject()->from7BitString( env[1] );
108 // from
109 QVarLengthArray<QByteArray, 16> addrList;
110 ImapParser::parseParenthesizedList( env[2], addrList );
111 if ( !addrList.isEmpty() )
112 parseAddrList( addrList, msg->from(), version, m_stringPool );
113 // sender
114 ImapParser::parseParenthesizedList( env[3], addrList );
115 if ( !addrList.isEmpty() )
116 parseAddrList( addrList, msg->sender(), version, m_stringPool );
117 // reply-to
118 ImapParser::parseParenthesizedList( env[4], addrList );
119 if ( !addrList.isEmpty() )
120 parseAddrList( addrList, msg->replyTo(), version, m_stringPool );
121 // to
122 ImapParser::parseParenthesizedList( env[5], addrList );
123 if ( !addrList.isEmpty() )
124 parseAddrList( addrList, msg->to(), version, m_stringPool );
125 // cc
126 ImapParser::parseParenthesizedList( env[6], addrList );
127 if ( !addrList.isEmpty() )
128 parseAddrList( addrList, msg->cc(), version, m_stringPool );
129 // bcc
130 ImapParser::parseParenthesizedList( env[7], addrList );
131 if ( !addrList.isEmpty() )
132 parseAddrList( addrList, msg->bcc(), version, m_stringPool );
133 // in-reply-to
134 msg->inReplyTo()->from7BitString( env[8] );
135 // message id
136 msg->messageID()->from7BitString( env[9] );
137 // references
138 if ( env.count() > 10 )
139 msg->references()->from7BitString( env[10] );
140 }
141
142 return true;
143}
144
145static QByteArray quoteImapListEntry( const QByteArray &b )
146{
147 if ( b.isEmpty() )
148 return "NIL";
149 return ImapParser::quote( b );
150}
151
152static QByteArray buildImapList( const QList<QByteArray> &list )
153{
154 if ( list.isEmpty() )
155 return "NIL";
156 return QByteArray( "(" ) + ImapParser::join( list, " " ) + QByteArray( ")" );
157}
158
159template <typename T> static QByteArray buildAddrStruct( T const *hdr )
160{
161 QList<QByteArray> addrList;
162 KMime::Types::Mailbox::List mb = hdr->mailboxes();
163 foreach ( const KMime::Types::Mailbox &mbox, mb ) {
164 QList<QByteArray> addrStruct;
165 addrStruct << quoteImapListEntry( mbox.name().toUtf8() );
166 addrStruct << quoteImapListEntry( QByteArray() );
167 addrStruct << quoteImapListEntry( mbox.addrSpec().localPart.toUtf8() );
168 addrStruct << quoteImapListEntry( mbox.addrSpec().domain.toUtf8() );
169 addrList << buildImapList( addrStruct );
170 }
171 return buildImapList( addrList );
172}
173
174void SerializerPluginMail::serialize( const Item& item, const QByteArray& label, QIODevice& data, int &version )
175{
176 version = 1;
177
178 boost::shared_ptr<Message> m = item.payload< boost::shared_ptr<Message> >();
179 if ( label == MessagePart::Body ) {
180 data.write( m->encodedContent() );
181 } else if ( label == MessagePart::Envelope ) {
182 QList<QByteArray> env;
183 env << quoteImapListEntry( m->date()->as7BitString( false ) );
184 env << quoteImapListEntry( m->subject()->as7BitString( false ) );
185 env << buildAddrStruct( m->from() );
186 env << buildAddrStruct( m->sender() );
187 env << buildAddrStruct( m->replyTo() );
188 env << buildAddrStruct( m->to() );
189 env << buildAddrStruct( m->cc() );
190 env << buildAddrStruct( m->bcc() );
191 env << quoteImapListEntry( m->inReplyTo()->as7BitString( false ) );
192 env << quoteImapListEntry( m->messageID()->as7BitString( false ) );
193 env << quoteImapListEntry( m->references()->as7BitString( false ) );
194 data.write( buildImapList( env ) );
195 } else if ( label == MessagePart::Header ) {
196 data.write( m->head() );
197 }
198}
199
200QSet<QByteArray> SerializerPluginMail::parts( const Item &item ) const
201{
202 QSet<QByteArray> set;
203
204 if ( !item.hasPayload<KMime::Message::Ptr>() ) {
205 return set;
206 }
207
208 KMime::Message::Ptr msg = item.payload<KMime::Message::Ptr>();
209 if ( !msg ) {
210 return set;
211 }
212
213 // FIXME: we really want "has any header" here, but the kmime api doesn't offer that yet
214 if ( msg->hasContent() || msg->hasHeader( "Message-ID" ) ) {
215 set << MessagePart::Envelope << MessagePart::Header;
216 if ( !msg->body().isEmpty() || !msg->contents().isEmpty() ) {
217 set << MessagePart::Body;
218 }
219 }
220 return set;
221}
222
223QString SerializerPluginMail::extractGid(const Item& item) const
224{
225 if (!item.hasPayload<KMime::Message::Ptr>())
226 return QString();
227 const KMime::Message::Ptr msg = item.payload<KMime::Message::Ptr>();
228 KMime::Headers::MessageID *mid = msg->messageID( false );
229 if (mid)
230 return mid->asUnicodeString();
231 return QString();
232}
233
234Q_EXPORT_PLUGIN2( akonadi_serializer_mail, SerializerPluginMail )
235
236#include "moc_akonadi_serializer_mail.cpp"
237