File indexing completed on 2024-04-28 05:19:26
0001 /* 0002 ktnefwriter.cpp 0003 0004 SPDX-FileCopyrightText: 2002 Bo Thorsen <bo@sonofthor.dk> 0005 0006 This file is part of KTNEF, the KDE TNEF support library/program. 0007 0008 SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 /** 0011 * @file 0012 * This file is part of the API for handling TNEF data and 0013 * defines the KTNEFWriter class. 0014 * 0015 * @author Bo Thorsen 0016 */ 0017 0018 #include "ktnefwriter.h" 0019 #include "ktnefdefs.h" 0020 #include "ktnefproperty.h" 0021 #include "ktnefpropertyset.h" 0022 0023 #include "ktnef_debug.h" 0024 0025 #include <QByteArray> 0026 #include <QDataStream> 0027 #include <QDateTime> 0028 #include <QIODevice> 0029 #include <QList> 0030 0031 #include <cassert> 0032 0033 using namespace KTnef; 0034 0035 /** 0036 * KTNEFWriterPrivate class that helps to provide binary compatibility between releases. 0037 * @internal 0038 */ 0039 //@cond PRIVATE 0040 class KTnef::KTNEFWriterPrivateData 0041 { 0042 public: 0043 KTNEFWriterPrivateData() 0044 : mFirstAttachNum(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()) 0045 { 0046 } 0047 0048 KTNEFPropertySet properties; 0049 const quint16 mFirstAttachNum; 0050 }; 0051 //@endcond 0052 0053 KTNEFWriter::KTNEFWriter() 0054 : d(new KTnef::KTNEFWriterPrivateData) 0055 { 0056 // This is not something the user should fiddle with 0057 // First set the TNEF version 0058 QVariant v(0x00010000); 0059 addProperty(attTNEFVERSION, atpDWORD, v); 0060 0061 // Now set the code page to something reasonable. TODO: Use the right one 0062 QVariant v1((quint32)0x4e4); 0063 QVariant v2((quint32)0x0); 0064 QList<QVariant> list; 0065 list << v1; 0066 list << v2; 0067 v = QVariant(list); 0068 addProperty(attOEMCODEPAGE, atpBYTE, list); 0069 } 0070 0071 KTNEFWriter::~KTNEFWriter() = default; 0072 0073 void KTNEFWriter::addProperty(int tag, int type, const QVariant &value) 0074 { 0075 d->properties.addProperty(tag, type, value); 0076 } 0077 0078 //@cond IGNORE 0079 void addToChecksum(quint32 i, quint16 &checksum) 0080 { 0081 checksum += i & 0xff; 0082 checksum += (i >> 8) & 0xff; 0083 checksum += (i >> 16) & 0xff; 0084 checksum += (i >> 24) & 0xff; 0085 } 0086 0087 void addToChecksum(QByteArray &cs, quint16 &checksum) 0088 { 0089 int len = cs.length(); 0090 for (int i = 0; i < len; i++) { 0091 checksum += (quint8)cs[i]; 0092 } 0093 } 0094 0095 void writeCString(QDataStream &stream, QByteArray &str) 0096 { 0097 stream.writeRawData(str.data(), str.length()); 0098 stream << (quint8)0; 0099 } 0100 0101 quint32 mergeTagAndType(quint32 tag, quint32 type) 0102 { 0103 return ((type & 0xffff) << 16) | (tag & 0xffff); 0104 } 0105 //@endcond 0106 0107 /* This writes a TNEF property to the file. 0108 * 0109 * A TNEF property has a 1 byte type (LVL_MESSAGE or LVL_ATTACHMENT), 0110 * a 4 byte type/tag, a 4 byte length, the data and finally the checksum. 0111 * 0112 * The checksum is a 16 byte int with all bytes in the data added. 0113 */ 0114 bool KTNEFWriter::writeProperty(QDataStream &stream, int &bytes, int tag) const 0115 { 0116 QMap<int, KTNEFProperty *> &properties = d->properties.properties(); 0117 QMap<int, KTNEFProperty *>::Iterator it = properties.find(tag); 0118 0119 if (it == properties.end()) { 0120 return false; 0121 } 0122 0123 KTNEFProperty *property = *it; 0124 0125 quint32 i; 0126 quint16 checksum = 0; 0127 QList<QVariant> list; 0128 QByteArray cs; 0129 QByteArray cs2; 0130 QDateTime dt; 0131 QDate date; 0132 QTime time; 0133 switch (tag) { 0134 case attMSGSTATUS: 0135 // quint8 0136 i = property->value().toUInt() & 0xff; 0137 checksum = i; 0138 0139 stream << (quint8)LVL_MESSAGE; 0140 stream << mergeTagAndType(tag, property->type()); 0141 stream << (quint32)1; 0142 stream << (quint8)i; 0143 0144 bytes += 10; 0145 break; 0146 0147 case attMSGPRIORITY: 0148 case attREQUESTRES: 0149 // quint16 0150 i = property->value().toUInt() & 0xffff; 0151 addToChecksum(i, checksum); 0152 0153 stream << (quint8)LVL_MESSAGE; 0154 stream << mergeTagAndType(tag, property->type()); 0155 stream << (quint32)2; 0156 stream << (quint16)i; 0157 0158 bytes += 11; 0159 break; 0160 0161 case attTNEFVERSION: 0162 // quint32 0163 i = property->value().toUInt(); 0164 addToChecksum(i, checksum); 0165 0166 stream << (quint8)LVL_MESSAGE; 0167 stream << mergeTagAndType(tag, property->type()); 0168 stream << (quint32)4; 0169 stream << (quint32)i; 0170 0171 bytes += 13; 0172 break; 0173 0174 case attOEMCODEPAGE: 0175 // 2 quint32 0176 list = property->value().toList(); 0177 assert(list.count() == 2); 0178 0179 stream << (quint8)LVL_MESSAGE; 0180 stream << mergeTagAndType(tag, property->type()); 0181 stream << (quint32)8; 0182 0183 i = list[0].toInt(); 0184 addToChecksum(i, checksum); 0185 stream << (quint32)i; 0186 i = list[1].toInt(); 0187 addToChecksum(i, checksum); 0188 stream << (quint32)i; 0189 0190 bytes += 17; 0191 break; 0192 0193 case attMSGCLASS: 0194 case attSUBJECT: 0195 case attBODY: 0196 case attMSGID: 0197 // QCString 0198 cs = property->value().toString().toLocal8Bit(); 0199 addToChecksum(cs, checksum); 0200 0201 stream << (quint8)LVL_MESSAGE; 0202 stream << mergeTagAndType(tag, property->type()); 0203 stream << (quint32)cs.length() + 1; 0204 writeCString(stream, cs); 0205 0206 bytes += 9 + cs.length() + 1; 0207 break; 0208 0209 case attFROM: 0210 // 2 QString encoded to a TRP structure 0211 list = property->value().toList(); 0212 assert(list.count() == 2); 0213 0214 cs = list[0].toString().toLocal8Bit(); // Name 0215 cs2 = QString(QLatin1StringView("smtp:") + list[1].toString()).toLocal8Bit(); // Email address 0216 i = 18 + cs.length() + cs2.length(); // 2 * sizof(TRP) + strings + 2x'\0' 0217 0218 stream << (quint8)LVL_MESSAGE; 0219 stream << mergeTagAndType(tag, property->type()); 0220 stream << (quint32)i; 0221 0222 // The stream has to be aligned to 4 bytes for the strings 0223 // TODO: Or does it? Looks like Outlook doesn't do this 0224 // bytes += 17; 0225 // Write the first TRP structure 0226 stream << (quint16)4; // trpidOneOff 0227 stream << (quint16)i; // totalsize 0228 stream << (quint16)(cs.length() + 1); // sizeof name 0229 stream << (quint16)(cs2.length() + 1); // sizeof address 0230 0231 // if ( bytes % 4 != 0 ) 0232 // Align the buffer 0233 0234 // Write the strings 0235 writeCString(stream, cs); 0236 writeCString(stream, cs2); 0237 0238 // Write the empty padding TRP structure (just zeroes) 0239 stream << (quint32)0 << (quint32)0; 0240 0241 addToChecksum(4, checksum); 0242 addToChecksum(i, checksum); 0243 addToChecksum(cs.length() + 1, checksum); 0244 addToChecksum(cs2.length() + 1, checksum); 0245 addToChecksum(cs, checksum); 0246 addToChecksum(cs2, checksum); 0247 0248 bytes += 10; 0249 break; 0250 0251 case attDATESENT: 0252 case attDATERECD: 0253 case attDATEMODIFIED: 0254 // QDateTime 0255 dt = property->value().toDateTime(); 0256 time = dt.time(); 0257 date = dt.date(); 0258 0259 stream << (quint8)LVL_MESSAGE; 0260 stream << mergeTagAndType(tag, property->type()); 0261 stream << (quint32)14; 0262 0263 i = (quint16)date.year(); 0264 addToChecksum(i, checksum); 0265 stream << (quint16)i; 0266 i = (quint16)date.month(); 0267 addToChecksum(i, checksum); 0268 stream << (quint16)i; 0269 i = (quint16)date.day(); 0270 addToChecksum(i, checksum); 0271 stream << (quint16)i; 0272 i = (quint16)time.hour(); 0273 addToChecksum(i, checksum); 0274 stream << (quint16)i; 0275 i = (quint16)time.minute(); 0276 addToChecksum(i, checksum); 0277 stream << (quint16)i; 0278 i = (quint16)time.second(); 0279 addToChecksum(i, checksum); 0280 stream << (quint16)i; 0281 i = (quint16)date.dayOfWeek(); 0282 addToChecksum(i, checksum); 0283 stream << (quint16)i; 0284 break; 0285 /* 0286 case attMSGSTATUS: 0287 { 0288 quint8 c; 0289 quint32 flag = 0; 0290 if ( c & fmsRead ) flag |= MSGFLAG_READ; 0291 if ( !( c & fmsModified ) ) flag |= MSGFLAG_UNMODIFIED; 0292 if ( c & fmsSubmitted ) flag |= MSGFLAG_SUBMIT; 0293 if ( c & fmsHasAttach ) flag |= MSGFLAG_HASATTACH; 0294 if ( c & fmsLocal ) flag |= MSGFLAG_UNSENT; 0295 d->stream_ >> c; 0296 0297 i = property->value().toUInt(); 0298 stream << (quint8)LVL_MESSAGE; 0299 stream << (quint32)type; 0300 stream << (quint32)2; 0301 stream << (quint8)i; 0302 addToChecksum( i, checksum ); 0303 // from reader: d->message_->addProperty( 0x0E07, MAPI_TYPE_ULONG, flag ); 0304 } 0305 qCDebug(KTNEF_LOG) << "Message Status" << "(length=" << i2 << ")"; 0306 break; 0307 */ 0308 0309 default: 0310 qCDebug(KTNEF_LOG) << "Unknown TNEF tag:" << tag; 0311 return false; 0312 } 0313 0314 stream << (quint16)checksum; 0315 return true; 0316 } 0317 0318 bool KTNEFWriter::writeFile(QIODevice &file) const 0319 { 0320 if (!file.open(QIODevice::WriteOnly)) { 0321 return false; 0322 } 0323 0324 QDataStream stream(&file); 0325 return writeFile(stream); 0326 } 0327 0328 bool KTNEFWriter::writeFile(QDataStream &stream) const 0329 { 0330 stream.setByteOrder(QDataStream::LittleEndian); 0331 0332 // Start by writing the opening TNEF stuff 0333 stream << TNEF_SIGNATURE; 0334 0335 // Store the PR_ATTACH_NUM value for the first attachment 0336 // ( must be stored even if *no* attachments are stored ) 0337 stream << d->mFirstAttachNum; 0338 0339 // Now do some writing 0340 bool ok = true; 0341 int bytesWritten = 0; 0342 ok &= writeProperty(stream, bytesWritten, attTNEFVERSION); 0343 ok &= writeProperty(stream, bytesWritten, attOEMCODEPAGE); 0344 ok &= writeProperty(stream, bytesWritten, attMSGCLASS); 0345 ok &= writeProperty(stream, bytesWritten, attMSGPRIORITY); 0346 ok &= writeProperty(stream, bytesWritten, attSUBJECT); 0347 ok &= writeProperty(stream, bytesWritten, attDATESENT); 0348 ok &= writeProperty(stream, bytesWritten, attDATESTART); 0349 ok &= writeProperty(stream, bytesWritten, attDATEEND); 0350 // ok &= writeProperty( stream, bytesWritten, attAIDOWNER ); 0351 ok &= writeProperty(stream, bytesWritten, attREQUESTRES); 0352 ok &= writeProperty(stream, bytesWritten, attFROM); 0353 ok &= writeProperty(stream, bytesWritten, attDATERECD); 0354 ok &= writeProperty(stream, bytesWritten, attMSGSTATUS); 0355 ok &= writeProperty(stream, bytesWritten, attBODY); 0356 return ok; 0357 } 0358 0359 void KTNEFWriter::setSender(const QString &name, const QString &email) 0360 { 0361 assert(!name.isEmpty()); 0362 assert(!email.isEmpty()); 0363 0364 QVariant v1(name); 0365 QVariant v2(email); 0366 0367 const QList<QVariant> list = {v1, v2}; 0368 0369 addProperty(attFROM, 0, list); // What's up with the 0 here ?? 0370 } 0371 0372 void KTNEFWriter::setMessageType(MessageType m) 0373 { 0374 // Note that the MessageType list here is probably not long enough, 0375 // more entries are most likely needed later 0376 0377 QVariant v; 0378 switch (m) { 0379 case Appointment: 0380 v = QVariant(QLatin1StringView("IPM.Appointment")); 0381 break; 0382 0383 case MeetingCancelled: 0384 v = QVariant(QLatin1StringView("IPM.Schedule.Meeting.Cancelled")); 0385 break; 0386 0387 case MeetingRequest: 0388 v = QVariant(QLatin1StringView("IPM.Schedule.Meeting.Request")); 0389 break; 0390 0391 case MeetingNo: 0392 v = QVariant(QLatin1StringView("IPM.Schedule.Meeting.Resp.Neg")); 0393 break; 0394 0395 case MeetingYes: 0396 v = QVariant(QLatin1StringView("IPM.Schedule.Meeting.Resp.Pos")); 0397 break; 0398 0399 case MeetingTent: 0400 // Tent? 0401 v = QVariant(QLatin1StringView("IPM.Schedule.Meeting.Resp.Tent")); 0402 break; 0403 0404 default: 0405 return; 0406 } 0407 0408 addProperty(attMSGCLASS, atpWORD, v); 0409 } 0410 0411 void KTNEFWriter::setMethod(Method) 0412 { 0413 } 0414 0415 void KTNEFWriter::clearAttendees() 0416 { 0417 } 0418 0419 void KTNEFWriter::addAttendee(const QString &cn, Role r, PartStat p, bool rsvp, const QString &mailto) 0420 { 0421 Q_UNUSED(cn) 0422 Q_UNUSED(r) 0423 Q_UNUSED(p) 0424 Q_UNUSED(rsvp) 0425 Q_UNUSED(mailto) 0426 } 0427 0428 // I assume this is the same as the sender? 0429 // U also assume that this is like "Name <address>" 0430 void KTNEFWriter::setOrganizer(const QString &organizer) 0431 { 0432 int i = organizer.indexOf(QLatin1Char('<')); 0433 0434 if (i == -1) { 0435 return; 0436 } 0437 0438 QString name = organizer.left(i).trimmed(); 0439 0440 QString email = organizer.right(i + 1); 0441 email = email.left(email.length() - 1).trimmed(); 0442 0443 setSender(name, email); 0444 } 0445 0446 void KTNEFWriter::setDtStart(const QDateTime &dtStart) 0447 { 0448 QVariant v(dtStart); 0449 addProperty(attDATESTART, atpDATE, v); 0450 } 0451 0452 void KTNEFWriter::setDtEnd(const QDateTime &dtEnd) 0453 { 0454 QVariant v(dtEnd); 0455 addProperty(attDATEEND, atpDATE, v); 0456 } 0457 0458 void KTNEFWriter::setLocation(const QString & /*location*/) 0459 { 0460 } 0461 0462 void KTNEFWriter::setUID(const QString &uid) 0463 { 0464 QVariant v(uid); 0465 addProperty(attMSGID, atpSTRING, v); 0466 } 0467 0468 // Date sent 0469 void KTNEFWriter::setDtStamp(const QDateTime &dtStamp) 0470 { 0471 QVariant v(dtStamp); 0472 addProperty(attDATESENT, atpDATE, v); 0473 } 0474 0475 void KTNEFWriter::setCategories(const QStringList &) 0476 { 0477 } 0478 0479 // I hope this is the body 0480 void KTNEFWriter::setDescription(const QString &body) 0481 { 0482 QVariant v(body); 0483 addProperty(attBODY, atpTEXT, v); 0484 } 0485 0486 void KTNEFWriter::setSummary(const QString &s) 0487 { 0488 QVariant v(s); 0489 addProperty(attSUBJECT, atpSTRING, v); 0490 } 0491 0492 // TNEF encoding: Normal = 3, high = 2, low = 1 0493 // MAPI encoding: Normal = -1, high = 0, low = 1 0494 void KTNEFWriter::setPriority(Priority p) 0495 { 0496 QVariant v((quint32)p); 0497 addProperty(attMSGPRIORITY, atpSHORT, v); 0498 } 0499 0500 void KTNEFWriter::setAlarm(const QString &description, AlarmAction action, const QDateTime &wakeBefore) 0501 { 0502 Q_UNUSED(description) 0503 Q_UNUSED(action) 0504 Q_UNUSED(wakeBefore) 0505 }