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 }