1/*
2 ktnefwriter.cpp
3
4 Copyright (C) 2002 Bo Thorsen <bo@sonofthor.dk>
5
6 This file is part of KTNEF, the KDE TNEF support library/program.
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22 */
23/**
24 * @file
25 * This file is part of the API for handling TNEF data and
26 * defines the KTNEFWriter class.
27 *
28 * @author Bo Thorsen
29 */
30
31#include "ktnefwriter.h"
32#include "ktnefproperty.h"
33#include "ktnefpropertyset.h"
34#include "ktnefdefs.h"
35
36#include <kdebug.h>
37
38#include <QtCore/QFile>
39#include <QtCore/QDateTime>
40#include <QtCore/QDataStream>
41#include <QtCore/QList>
42#include <QtCore/QByteArray>
43
44#include <assert.h>
45
46using namespace KTnef;
47
48/**
49 * Private class that helps to provide binary compatibility between releases.
50 * @internal
51 */
52//@cond PRIVATE
53class KTnef::KTNEFWriter::PrivateData
54{
55 public:
56 PrivateData() { mFirstAttachNum = QDateTime::currentDateTime().toTime_t(); }
57 KTNEFPropertySet properties;
58 quint16 mFirstAttachNum;
59};
60//@endcond
61
62KTNEFWriter::KTNEFWriter() : d( new KTnef::KTNEFWriter::PrivateData )
63{
64 // This is not something the user should fiddle with
65 // First set the TNEF version
66 QVariant v(0x00010000);
67 addProperty( attTNEFVERSION, atpDWORD, v );
68
69 // Now set the code page to something reasonable. TODO: Use the right one
70 QVariant v1( (quint32)0x4e4 );
71 QVariant v2( (quint32)0x0 );
72 QList<QVariant> list;
73 list << v1;
74 list << v2;
75 v = QVariant( list );
76 addProperty( attOEMCODEPAGE, atpBYTE, list );
77}
78
79KTNEFWriter::~KTNEFWriter()
80{
81 delete d;
82}
83
84void KTNEFWriter::addProperty( int tag, int type, const QVariant &value )
85{
86 d->properties.addProperty( tag, type, value );
87}
88
89//@cond IGNORE
90void addToChecksum( quint32 i, quint16 &checksum )
91{
92 checksum += i & 0xff;
93 checksum += ( i >> 8 ) & 0xff;
94 checksum += ( i >> 16 ) & 0xff;
95 checksum += ( i >> 24 ) & 0xff;
96}
97
98void addToChecksum( QByteArray &cs, quint16 &checksum )
99{
100 int len = cs.length();
101 for ( int i=0; i<len; i++ ) {
102 checksum += (quint8)cs[i];
103 }
104}
105
106void writeCString( QDataStream &stream, QByteArray &str )
107{
108 stream.writeRawData( str.data(), str.length() );
109 stream << (quint8)0;
110}
111
112quint32 mergeTagAndType( quint32 tag, quint32 type )
113{
114 return ( ( type & 0xffff ) << 16 ) | ( tag & 0xffff );
115}
116//@endcond
117
118/* This writes a TNEF property to the file.
119 *
120 * A TNEF property has a 1 byte type (LVL_MESSAGE or LVL_ATTACHMENT),
121 * a 4 byte type/tag, a 4 byte length, the data and finally the checksum.
122 *
123 * The checksum is a 16 byte int with all bytes in the data added.
124 */
125bool KTNEFWriter::writeProperty( QDataStream &stream, int &bytes, int tag ) const
126{
127 QMap<int,KTNEFProperty*>& properties = d->properties.properties();
128 QMap<int,KTNEFProperty*>::Iterator it = properties.find( tag );
129
130 if ( it == properties.end() ) {
131 return false;
132 }
133
134 KTNEFProperty *property = *it;
135
136 quint32 i;
137 quint16 checksum = 0;
138 QList<QVariant> list;
139 QString s;
140 QByteArray cs, cs2;
141 QDateTime dt;
142 QDate date;
143 QTime time;
144 switch( tag ) {
145 case attMSGSTATUS:
146 // quint8
147 i = property->value().toUInt() & 0xff;
148 checksum = i;
149
150 stream << (quint8)LVL_MESSAGE;
151 stream << mergeTagAndType( tag, property->type() );
152 stream << (quint32)1;
153 stream << (quint8)i;
154
155 bytes += 10;
156 break;
157
158 case attMSGPRIORITY:
159 case attREQUESTRES:
160 // quint16
161 i = property->value().toUInt() & 0xffff;
162 addToChecksum( i, checksum );
163
164 stream << (quint8)LVL_MESSAGE;
165 stream << mergeTagAndType( tag, property->type() );
166 stream << (quint32)2;
167 stream << (quint16)i;
168
169 bytes += 11;
170 break;
171
172 case attTNEFVERSION:
173 // quint32
174 i = property->value().toUInt();
175 addToChecksum( i, checksum );
176
177 stream << (quint8)LVL_MESSAGE;
178 stream << mergeTagAndType( tag, property->type() );
179 stream << (quint32)4;
180 stream << (quint32)i;
181
182 bytes += 13;
183 break;
184
185 case attOEMCODEPAGE:
186 // 2 quint32
187 list = property->value().toList();
188 assert( list.count() == 2 );
189
190 stream << (quint8)LVL_MESSAGE;
191 stream << mergeTagAndType( tag, property->type() );
192 stream << (quint32)8;
193
194 i = list[0].toInt();
195 addToChecksum( i, checksum );
196 stream << (quint32)i;
197 i = list[1].toInt();
198 addToChecksum( i, checksum );
199 stream << (quint32)i;
200
201 bytes += 17;
202 break;
203
204 case attMSGCLASS:
205 case attSUBJECT:
206 case attBODY:
207 case attMSGID:
208 // QCString
209 cs = property->value().toString().toLocal8Bit();
210 addToChecksum( cs, checksum );
211
212 stream << (quint8)LVL_MESSAGE;
213 stream << mergeTagAndType( tag, property->type() );
214 stream << (quint32)cs.length()+1;
215 writeCString( stream, cs );
216
217 bytes += 9 + cs.length()+1;
218 break;
219
220 case attFROM:
221 // 2 QString encoded to a TRP structure
222 list = property->value().toList();
223 assert( list.count() == 2 );
224
225 cs = list[0].toString().toLocal8Bit(); // Name
226 cs2 = QString( QLatin1String( "smtp:" ) + list[1].toString() ).toLocal8Bit(); // Email address
227 i = 18 + cs.length() + cs2.length(); // 2 * sizof(TRP) + strings + 2x'\0'
228
229 stream << (quint8)LVL_MESSAGE;
230 stream << mergeTagAndType( tag, property->type() );
231 stream << (quint32)i;
232
233 // The stream has to be aligned to 4 bytes for the strings
234 // TODO: Or does it? Looks like Outlook doesn't do this
235 // bytes += 17;
236 // Write the first TRP structure
237 stream << (quint16)4; // trpidOneOff
238 stream << (quint16)i; // totalsize
239 stream << (quint16)( cs.length() + 1 ); // sizeof name
240 stream << (quint16)( cs2.length() + 1 );// sizeof address
241
242 // if ( bytes % 4 != 0 )
243 // Align the buffer
244
245 // Write the strings
246 writeCString( stream, cs );
247 writeCString( stream, cs2 );
248
249 // Write the empty padding TRP structure (just zeroes)
250 stream << (quint32)0 << (quint32)0;
251
252 addToChecksum( 4, checksum );
253 addToChecksum( i, checksum );
254 addToChecksum( cs.length()+1, checksum );
255 addToChecksum( cs2.length()+1, checksum );
256 addToChecksum( cs, checksum );
257 addToChecksum( cs2, checksum );
258
259 bytes += 10;
260 break;
261
262 case attDATESENT:
263 case attDATERECD:
264 case attDATEMODIFIED:
265 // QDateTime
266 dt = property->value().toDateTime();
267 time = dt.time();
268 date = dt.date();
269
270 stream << (quint8)LVL_MESSAGE;
271 stream << mergeTagAndType( tag, property->type() );
272 stream << (quint32)14;
273
274 i = (quint16)date.year();
275 addToChecksum( i, checksum );
276 stream << (quint16)i;
277 i = (quint16)date.month();
278 addToChecksum( i, checksum );
279 stream << (quint16)i;
280 i = (quint16)date.day();
281 addToChecksum( i, checksum );
282 stream << (quint16)i;
283 i = (quint16)time.hour();
284 addToChecksum( i, checksum );
285 stream << (quint16)i;
286 i = (quint16)time.minute();
287 addToChecksum( i, checksum );
288 stream << (quint16)i;
289 i = (quint16)time.second();
290 addToChecksum( i, checksum );
291 stream << (quint16)i;
292 i = (quint16)date.dayOfWeek();
293 addToChecksum( i, checksum );
294 stream << (quint16)i;
295 break;
296/*
297 case attMSGSTATUS:
298 {
299 quint8 c;
300 quint32 flag = 0;
301 if ( c & fmsRead ) flag |= MSGFLAG_READ;
302 if ( !( c & fmsModified ) ) flag |= MSGFLAG_UNMODIFIED;
303 if ( c & fmsSubmitted ) flag |= MSGFLAG_SUBMIT;
304 if ( c & fmsHasAttach ) flag |= MSGFLAG_HASATTACH;
305 if ( c & fmsLocal ) flag |= MSGFLAG_UNSENT;
306 d->stream_ >> c;
307
308 i = property->value().toUInt();
309 stream << (quint8)LVL_MESSAGE;
310 stream << (quint32)type;
311 stream << (quint32)2;
312 stream << (quint8)i;
313 addToChecksum( i, checksum );
314 // from reader: d->message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag );
315 }
316 kDebug() << "Message Status" << "(length=" << i2 << ")";
317 break;
318*/
319
320 default:
321 kDebug() << "Unknown TNEF tag:" << tag;
322 return false;
323 }
324
325 stream << (quint16)checksum;
326 return true;
327}
328
329bool KTNEFWriter::writeFile( QIODevice &file ) const
330{
331 if ( !file.open( QIODevice::WriteOnly ) ) {
332 return false;
333 }
334
335 QDataStream stream( &file );
336 return writeFile( stream );
337}
338
339bool KTNEFWriter::writeFile( QDataStream &stream ) const
340{
341 stream.setByteOrder( QDataStream::LittleEndian );
342
343 // Start by writing the opening TNEF stuff
344 stream << TNEF_SIGNATURE;
345
346 // Store the PR_ATTACH_NUM value for the first attachment
347 // ( must be stored even if *no* attachments are stored )
348 stream << d->mFirstAttachNum;
349
350 // Now do some writing
351 bool ok = true;
352 int bytesWritten = 0;
353 ok &= writeProperty( stream, bytesWritten, attTNEFVERSION );
354 ok &= writeProperty( stream, bytesWritten, attOEMCODEPAGE );
355 ok &= writeProperty( stream, bytesWritten, attMSGCLASS );
356 ok &= writeProperty( stream, bytesWritten, attMSGPRIORITY );
357 ok &= writeProperty( stream, bytesWritten, attSUBJECT );
358 ok &= writeProperty( stream, bytesWritten, attDATESENT );
359 ok &= writeProperty( stream, bytesWritten, attDATESTART );
360 ok &= writeProperty( stream, bytesWritten, attDATEEND );
361 // ok &= writeProperty( stream, bytesWritten, attAIDOWNER );
362 ok &= writeProperty( stream, bytesWritten, attREQUESTRES );
363 ok &= writeProperty( stream, bytesWritten, attFROM );
364 ok &= writeProperty( stream, bytesWritten, attDATERECD );
365 ok &= writeProperty( stream, bytesWritten, attMSGSTATUS );
366 ok &= writeProperty( stream, bytesWritten, attBODY );
367 return ok;
368}
369
370void KTNEFWriter::setSender( const QString &name, const QString &email )
371{
372 assert( !name.isEmpty() );
373 assert( !email.isEmpty() );
374
375 QVariant v1( name );
376 QVariant v2( email );
377
378 QList<QVariant> list;
379 list << v1;
380 list << v2;
381
382 QVariant v( list );
383 addProperty( attFROM, 0, list ); // What's up with the 0 here ??
384}
385
386void KTNEFWriter::setMessageType( MessageType m )
387{
388 // Note that the MessageType list here is probably not long enough,
389 // more entries are most likely needed later
390
391 QVariant v;
392 switch( m ) {
393 case Appointment:
394 v = QVariant( QString( "IPM.Appointment" ) );
395 break;
396
397 case MeetingCancelled:
398 v = QVariant( QString( "IPM.Schedule.Meeting.Cancelled" ) );
399 break;
400
401 case MeetingRequest:
402 v = QVariant( QString( "IPM.Schedule.Meeting.Request" ) );
403 break;
404
405 case MeetingNo:
406 v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Neg" ) );
407 break;
408
409 case MeetingYes:
410 v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Pos" ) );
411 break;
412
413 case MeetingTent:
414 // Tent?
415 v = QVariant( QString( "IPM.Schedule.Meeting.Resp.Tent" ) );
416 break;
417
418 default:
419 return;
420 }
421
422 addProperty( attMSGCLASS, atpWORD, v );
423}
424
425void KTNEFWriter::setMethod( Method )
426{
427
428}
429
430void KTNEFWriter::clearAttendees()
431{
432
433}
434
435void KTNEFWriter::addAttendee( const QString &cn, Role r,
436 PartStat p, bool rsvp,
437 const QString &mailto )
438{
439 Q_UNUSED( cn );
440 Q_UNUSED( r );
441 Q_UNUSED( p );
442 Q_UNUSED( rsvp );
443 Q_UNUSED( mailto );
444}
445
446// I assume this is the same as the sender?
447// U also assume that this is like "Name <address>"
448void KTNEFWriter::setOrganizer( const QString &organizer )
449{
450 int i = organizer.indexOf( '<' );
451
452 if ( i == -1 ) {
453 return;
454 }
455
456 QString name = organizer.left( i ).trimmed();
457
458 QString email = organizer.right( i+1 );
459 email = email.left( email.length()-1 ).trimmed();
460
461 setSender( name, email );
462}
463
464void KTNEFWriter::setDtStart( const QDateTime &dtStart )
465{
466 QVariant v( dtStart );
467 addProperty( attDATESTART, atpDATE, v );
468}
469
470void KTNEFWriter::setDtEnd( const QDateTime &dtEnd )
471{
472 QVariant v( dtEnd );
473 addProperty( attDATEEND, atpDATE, v );
474}
475
476void KTNEFWriter::setLocation( const QString &/*location*/ )
477{
478
479}
480
481void KTNEFWriter::setUID( const QString &uid )
482{
483 QVariant v( uid );
484 addProperty( attMSGID, atpSTRING, v );
485}
486
487// Date sent
488void KTNEFWriter::setDtStamp( const QDateTime &dtStamp )
489{
490 QVariant v( dtStamp );
491 addProperty( attDATESENT, atpDATE, v );
492}
493
494void KTNEFWriter::setCategories( const QStringList &)
495{
496
497}
498
499// I hope this is the body
500void KTNEFWriter::setDescription( const QString &body )
501{
502 QVariant v( body );
503 addProperty( attBODY, atpTEXT, v );
504}
505
506void KTNEFWriter::setSummary( const QString &s )
507{
508 QVariant v( s );
509 addProperty( attSUBJECT, atpSTRING, v );
510}
511
512// TNEF encoding: Normal = 3, high = 2, low = 1
513// MAPI encoding: Normal = -1, high = 0, low = 1
514void KTNEFWriter::setPriority( Priority p )
515{
516 QVariant v( (quint32)p );
517 addProperty( attMSGPRIORITY, atpSHORT, v );
518}
519
520void KTNEFWriter::setAlarm( const QString &description,
521 AlarmAction action,
522 const QDateTime &wakeBefore )
523{
524 Q_UNUSED( description );
525 Q_UNUSED( action );
526 Q_UNUSED( wakeBefore );
527}
528