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 | |
46 | using namespace KTnef; |
47 | |
48 | /** |
49 | * Private class that helps to provide binary compatibility between releases. |
50 | * @internal |
51 | */ |
52 | //@cond PRIVATE |
53 | class KTnef::KTNEFWriter::PrivateData |
54 | { |
55 | public: |
56 | PrivateData() { mFirstAttachNum = QDateTime::currentDateTime().toTime_t(); } |
57 | KTNEFPropertySet properties; |
58 | quint16 mFirstAttachNum; |
59 | }; |
60 | //@endcond |
61 | |
62 | KTNEFWriter::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 | |
79 | KTNEFWriter::~KTNEFWriter() |
80 | { |
81 | delete d; |
82 | } |
83 | |
84 | void KTNEFWriter::addProperty( int tag, int type, const QVariant &value ) |
85 | { |
86 | d->properties.addProperty( tag, type, value ); |
87 | } |
88 | |
89 | //@cond IGNORE |
90 | void 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 | |
98 | void 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 | |
106 | void writeCString( QDataStream &stream, QByteArray &str ) |
107 | { |
108 | stream.writeRawData( str.data(), str.length() ); |
109 | stream << (quint8)0; |
110 | } |
111 | |
112 | quint32 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 | */ |
125 | bool 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 | |
329 | bool 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 | |
339 | bool 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 | |
370 | void 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 | |
386 | void 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 | |
425 | void KTNEFWriter::setMethod( Method ) |
426 | { |
427 | |
428 | } |
429 | |
430 | void KTNEFWriter::clearAttendees() |
431 | { |
432 | |
433 | } |
434 | |
435 | void 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>" |
448 | void 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 | |
464 | void KTNEFWriter::setDtStart( const QDateTime &dtStart ) |
465 | { |
466 | QVariant v( dtStart ); |
467 | addProperty( attDATESTART, atpDATE, v ); |
468 | } |
469 | |
470 | void KTNEFWriter::setDtEnd( const QDateTime &dtEnd ) |
471 | { |
472 | QVariant v( dtEnd ); |
473 | addProperty( attDATEEND, atpDATE, v ); |
474 | } |
475 | |
476 | void KTNEFWriter::setLocation( const QString &/*location*/ ) |
477 | { |
478 | |
479 | } |
480 | |
481 | void KTNEFWriter::setUID( const QString &uid ) |
482 | { |
483 | QVariant v( uid ); |
484 | addProperty( attMSGID, atpSTRING, v ); |
485 | } |
486 | |
487 | // Date sent |
488 | void KTNEFWriter::setDtStamp( const QDateTime &dtStamp ) |
489 | { |
490 | QVariant v( dtStamp ); |
491 | addProperty( attDATESENT, atpDATE, v ); |
492 | } |
493 | |
494 | void KTNEFWriter::setCategories( const QStringList &) |
495 | { |
496 | |
497 | } |
498 | |
499 | // I hope this is the body |
500 | void KTNEFWriter::setDescription( const QString &body ) |
501 | { |
502 | QVariant v( body ); |
503 | addProperty( attBODY, atpTEXT, v ); |
504 | } |
505 | |
506 | void 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 |
514 | void KTNEFWriter::setPriority( Priority p ) |
515 | { |
516 | QVariant v( (quint32)p ); |
517 | addProperty( attMSGPRIORITY, atpSHORT, v ); |
518 | } |
519 | |
520 | void 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 | |