24#include "ktnef_debug.h"
25#include <QMimeDatabase>
35#include <QStandardPaths>
53void clearMAPIName(MAPI_value &mapi);
54void clearMAPIValue(MAPI_value &mapi,
bool clearName =
true);
55QString readMAPIString(
QDataStream &stream,
bool isUnicode =
false,
bool align =
true,
int len = -1);
56quint16 readMAPIValue(
QDataStream &stream, MAPI_value &mapi);
61QDateTime formatTime(quint32 lowB, quint32 highB);
72class KTnef::KTNEFParser::ParserPrivate
85 bool decodeAttachment();
88 void checkCurrent(
int key);
98 bool deleteDevice_ =
false;
103 : d(new ParserPrivate)
117void KTNEFParser::ParserPrivate::deleteDevice()
123 deleteDevice_ =
false;
126bool KTNEFParser::ParserPrivate::decodeMessage()
139 tag = (i1 & 0x0000FFFF);
140 type = ((i1 & 0xFFFF0000) >> 16);
144 off = device_->pos() + i2;
150 message_->addProperty(0x0062, MAPI_TYPE_ULONG, value);
151 qCDebug(KTNEF_LOG) <<
"Message Owner Appointment ID"
152 <<
"(length=" << i2 <<
")";
157 message_->addProperty(0x0063, MAPI_TYPE_UINT16, u);
159 qCDebug(KTNEF_LOG) <<
"Message Request Response"
160 <<
"(length=" << i2 <<
")";
163 value = readTNEFDate(stream_);
164 message_->addProperty(0x0E06, MAPI_TYPE_TIME, value);
165 qCDebug(KTNEF_LOG) <<
"Message Receive Date"
166 <<
"(length=" << i2 <<
")";
169 value = readMAPIString(stream_,
false,
false, i2);
170 message_->addProperty(0x001A, MAPI_TYPE_STRING8, value);
171 qCDebug(KTNEF_LOG) <<
"Message Class"
172 <<
"(length=" << i2 <<
")";
176 message_->addProperty(0x0026, MAPI_TYPE_ULONG, 2 - u);
178 qCDebug(KTNEF_LOG) <<
"Message Priority"
179 <<
"(length=" << i2 <<
")";
182 qCDebug(KTNEF_LOG) <<
"Message MAPI Properties"
183 <<
"(length=" << i2 <<
")";
185 int nProps = message_->properties().count();
186 i2 += device_->pos();
187 readMAPIProperties(message_->properties(),
nullptr);
189 qCDebug(KTNEF_LOG) <<
"Properties:" << message_->properties().count();
190 value = QStringLiteral(
"< %1 properties >").arg(message_->properties().count() - nProps);
193 case attTNEFVERSION: {
197 qCDebug(KTNEF_LOG) <<
"Message TNEF Version"
198 <<
"(length=" << i2 <<
")";
201 message_->addProperty(0x0024, MAPI_TYPE_STRING8, readTNEFAddress(stream_));
202 device_->seek(device_->pos() - i2);
203 value = readTNEFData(stream_, i2);
204 qCDebug(KTNEF_LOG) <<
"Message From"
205 <<
"(length=" << i2 <<
")";
208 value = readMAPIString(stream_,
false,
false, i2);
209 message_->addProperty(0x0037, MAPI_TYPE_STRING8, value);
210 qCDebug(KTNEF_LOG) <<
"Message Subject"
211 <<
"(length=" << i2 <<
")";
214 value = readTNEFDate(stream_);
215 message_->addProperty(0x0039, MAPI_TYPE_TIME, value);
216 qCDebug(KTNEF_LOG) <<
"Message Date Sent"
217 <<
"(length=" << i2 <<
")";
224 flag |= MSGFLAG_READ;
226 if (!(c & fmsModified)) {
227 flag |= MSGFLAG_UNMODIFIED;
229 if (c & fmsSubmitted) {
230 flag |= MSGFLAG_SUBMIT;
232 if (c & fmsHasAttach) {
233 flag |= MSGFLAG_HASATTACH;
236 flag |= MSGFLAG_UNSENT;
238 message_->addProperty(0x0E07, MAPI_TYPE_ULONG, flag);
241 qCDebug(KTNEF_LOG) <<
"Message Status"
242 <<
"(length=" << i2 <<
")";
244 case attRECIPTABLE: {
248 if (rows > (INT_MAX /
sizeof(
QVariant))) {
252 for (uint i = 0; i < rows; i++) {
254 readMAPIProperties(props,
nullptr);
255 recipTable << formatRecipient(props);
257 message_->addProperty(0x0E12, MAPI_TYPE_STRING8, recipTable);
258 device_->seek(device_->pos() - i2);
259 value = readTNEFData(stream_, i2);
261 qCDebug(KTNEF_LOG) <<
"Message Recipient Table"
262 <<
"(length=" << i2 <<
")";
265 value = readMAPIString(stream_,
false,
false, i2);
266 message_->addProperty(0x1000, MAPI_TYPE_STRING8, value);
267 qCDebug(KTNEF_LOG) <<
"Message Body"
268 <<
"(length=" << i2 <<
")";
270 case attDATEMODIFIED:
271 value = readTNEFDate(stream_);
272 message_->addProperty(0x3008, MAPI_TYPE_TIME, value);
273 qCDebug(KTNEF_LOG) <<
"Message Date Modified"
274 <<
"(length=" << i2 <<
")";
277 value = readMAPIString(stream_,
false,
false, i2);
278 message_->addProperty(0x300B, MAPI_TYPE_STRING8, value);
279 qCDebug(KTNEF_LOG) <<
"Message ID"
280 <<
"(length=" << i2 <<
")";
283 value = readTNEFData(stream_, i2);
284 qCDebug(KTNEF_LOG) <<
"Message OEM Code Page"
285 <<
"(length=" << i2 <<
")";
288 value = readTNEFAttribute(stream_, type, i2);
293 if (device_->pos() != off && !device_->seek(off)) {
299 message_->addAttribute(tag, type, value,
true);
304bool KTNEFParser::ParserPrivate::decodeAttachment()
314 tag = (i & 0x0000FFFF);
315 type = ((i & 0xFFFF0000) >> 16);
320 value = readMAPIString(stream_,
false,
false, i);
321 current_->setName(value.
toString());
322 qCDebug(KTNEF_LOG) <<
"Attachment Title:" << current_->name();
325 current_->setSize(i);
326 current_->setOffset(device_->pos());
327 device_->seek(device_->pos() + i);
328 value = QStringLiteral(
"< size=%1 >").arg(i);
329 qCDebug(KTNEF_LOG) <<
"Attachment Data: size=" << i;
333 readMAPIProperties(current_->properties(), current_);
335 current_->setIndex(current_->property(MAPI_TAG_INDEX).toUInt());
336 current_->setDisplaySize(current_->property(MAPI_TAG_SIZE).toUInt());
337 str = current_->property(MAPI_TAG_DISPLAYNAME).toString();
339 current_->setDisplayName(str);
341 current_->setFileName(current_->property(MAPI_TAG_FILENAME).toString());
342 str = current_->property(MAPI_TAG_MIMETAG).toString();
344 current_->setMimeTag(str);
346 current_->setExtension(current_->property(MAPI_TAG_EXTENSION).toString());
347 value = QStringLiteral(
"< %1 properties >").arg(current_->properties().count());
349 case attATTACHMODDATE:
350 value = readTNEFDate(stream_);
351 qCDebug(KTNEF_LOG) <<
"Attachment Modification Date:" << value.
toString();
353 case attATTACHCREATEDATE:
354 value = readTNEFDate(stream_);
355 qCDebug(KTNEF_LOG) <<
"Attachment Creation Date:" << value.
toString();
357 case attATTACHMETAFILE:
358 qCDebug(KTNEF_LOG) <<
"Attachment Metafile: size=" << i;
361 value = readTNEFData(stream_, i);
364 value = readTNEFAttribute(stream_, type, i);
365 qCDebug(KTNEF_LOG) <<
"Attachment unknown field: tag=" <<
Qt::hex << tag <<
", length=" <<
Qt::dec << i;
370 current_->addAttribute(tag, type, value,
true);
378 d->defaultdir_ = dirname;
381bool KTNEFParser::ParserPrivate::parseDevice()
387 message_->clearAttachments();
391 if (!device_->isOpen()) {
393 qCDebug(KTNEF_LOG) <<
"Couldn't open device";
397 if (!device_->isReadable()) {
398 qCDebug(KTNEF_LOG) <<
"Device not readable";
402 stream_.setDevice(device_);
405 if (i == TNEF_SIGNATURE) {
407 qCDebug(KTNEF_LOG).nospace() <<
"Attachment cross reference key: 0x" <<
Qt::hex << qSetFieldWidth(4) << qSetPadChar(
QLatin1Char(
'0')) << u;
409 while (!stream_.atEnd()) {
413 if (!decodeMessage()) {
418 if (!decodeAttachment()) {
423 qCDebug(KTNEF_LOG) <<
"Unknown Level:" << c <<
", at offset" << device_->pos();
428 checkCurrent(attATTACHDATA);
436 qCDebug(KTNEF_LOG) <<
"This is not a TNEF file";
445 KTNEFAttach *att = d->message_->attachment(filename);
449 return d->extractAttachmentTo(att, d->defaultdir_);
452bool KTNEFParser::ParserPrivate::extractAttachmentTo(
KTNEFAttach *att,
const QString &dirname)
454 const QString destDir(
QDir(dirname).absolutePath());
460 filename += att->
name();
466 if (!device_->isOpen()) {
469 if (!device_->seek(att->
offset())) {
474 if (!fi.absoluteFilePath().startsWith(destDir)) {
475 qWarning() <<
"Attempted extract into" << fi.absoluteFilePath() <<
"which is outside of the extraction root folder" << destDir <<
"."
476 <<
"Changing export of contained files to extraction root folder.";
477 filename = destDir +
QLatin1Char(
'/') + fi.fileName();
485 quint32 len = att->
size();
487 char *buf =
new char[sz];
489 while (ok && len > 0) {
490 const int n = device_->read(buf, qMin(sz, len));
495 if (outfile.write(buf, n) != n) {
511 for (; it != itEnd; ++it) {
512 if (!d->extractAttachmentTo(*it, d->defaultdir_)) {
521 qCDebug(KTNEF_LOG) <<
"Extracting attachment: filename=" << filename <<
", dir=" << dirname;
522 KTNEFAttach *att = d->message_->attachment(filename);
526 return d->extractAttachmentTo(att, dirname);
534 auto file =
new QFile(filename);
536 d->deleteDevice_ =
true;
537 if (!file->exists()) {
540 return d->parseDevice();
547 return d->parseDevice();
550void KTNEFParser::ParserPrivate::checkCurrent(
int key)
555 if (current_->attributes().contains(key)) {
556 if (current_->offset() >= 0) {
557 if (current_->name().isEmpty()) {
558 current_->setName(QStringLiteral(
"Unnamed"));
560 if (current_->mimeTag().isEmpty()) {
566 if (!current_->fileName().isEmpty()) {
573 qint64 oldOffset = device_->pos();
574 QByteArray buffer(qMin(32, current_->size()),
'\0');
575 device_->seek(current_->offset());
576 device_->read(buffer.data(), buffer.size());
578 device_->seek(oldOffset);
580 current_->setMimeTag(
mimetype.name());
582 message_->addAttachment(current_);
599 n = (n + b) & ~(b - 1); \
601#define ISVECTOR(m) (((m).type & 0xF000) == MAPI_TYPE_VECTOR)
603void clearMAPIName(MAPI_value &mapi)
605 mapi.name.value.clear();
608void clearMAPIValue(MAPI_value &mapi,
bool clearName)
616QDateTime formatTime(quint32 lowB, quint32 highB)
623 u64 -= 116444736000000000LL;
625 if (u64 <= 0xffffffffU) {
628 qCWarning(KTNEF_LOG).nospace() <<
"Invalid date: low byte=" <<
Qt::showbase << qSetFieldWidth(8) << qSetPadChar(
QLatin1Char(
'0')) << lowB
629 <<
", high byte=" << highB;
641 if ((it = props.
find(0x3001)) != props.
end()) {
642 dn = (*it)->valueString();
644 if ((it = props.
find(0x3003)) != props.
end()) {
645 addr = (*it)->valueString();
647 if ((it = props.
find(0x0C15)) != props.
end()) {
648 switch ((*it)->value().toInt()) {
650 t = QStringLiteral(
"From:");
653 t = QStringLiteral(
"To:");
656 t = QStringLiteral(
"Cc:");
659 t = QStringLiteral(
"Bcc:");
669 if (!addr.
isEmpty() && addr != dn) {
686 stream >> y >> m >> d >> hh >> mm >> ss >> dm;
696 stream >> totalLen >> totalLen >> strLen >> addrLen;
697 s.
append(readMAPIString(stream,
false,
false, strLen));
699 s.
append(readMAPIString(stream,
false,
false, addrLen));
702 for (
int i = 8 + strLen + addrLen; i < totalLen; i++) {
722 return readMAPIString(stream,
false,
false, len);
724 return readTNEFDate(stream);
726 return readTNEFData(stream, len);
743 quint32 fullLen = len;
750 for (uint i = len; i < fullLen; i++) {
763quint16 readMAPIValue(
QDataStream &stream, MAPI_value &mapi)
767 clearMAPIValue(mapi);
769 mapi.type = (d & 0x0000FFFF);
770 mapi.tag = ((d & 0xFFFF0000) >> 16);
771 if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
773 stream >> d >> d >> d >> d;
775 stream >> mapi.name.type;
777 if (mapi.name.type == 0) {
780 mapi.name.value.setValue(tmp);
781 }
else if (mapi.name.type == 1) {
782 mapi.name.value.setValue(readMAPIString(stream,
true));
788 if (ISVECTOR(mapi)) {
792 for (
int i = 0; i < n; i++) {
794 switch (mapi.type & 0x0FFF) {
795 case MAPI_TYPE_UINT16:
799 case MAPI_TYPE_BOOLEAN:
800 case MAPI_TYPE_ULONG: {
805 case MAPI_TYPE_FLOAT:
809 case MAPI_TYPE_DOUBLE: {
814 case MAPI_TYPE_TIME: {
817 stream >> lowB >> highB;
818 value = formatTime(lowB, highB);
820 case MAPI_TYPE_USTRING:
821 case MAPI_TYPE_STRING8:
824 if (ISVECTOR(mapi)) {
829 for (uint j = 0; j < d; j++) {
831 value.
setValue(readMAPIString(stream, (mapi.type & 0x0FFF) == MAPI_TYPE_USTRING));
834 case MAPI_TYPE_OBJECT:
835 case MAPI_TYPE_BINARY:
836 if (ISVECTOR(mapi)) {
841 for (uint i = 0; i < d && !stream.
atEnd(); i++) {
846 if (len > 0 && len <= INT_MAX) {
849 stream.readRawData(value.toByteArray().data(), len);
851 for (uint i = len; i < fullLen; i++) {
859 mapi.type = MAPI_TYPE_NONE;
862 if (ISVECTOR(mapi)) {
865 mapi.
value.setValue(lst);
880 bool foundAttachment =
false;
883 mapi.type = MAPI_TYPE_NONE;
888 qCDebug(KTNEF_LOG) <<
"MAPI Properties:" << n;
889 for (uint i = 0; i < n; i++) {
890 if (stream_.atEnd()) {
891 clearMAPIValue(mapi);
894 readMAPIValue(stream_, mapi);
895 if (mapi.type == MAPI_TYPE_NONE) {
896 qCDebug(KTNEF_LOG).nospace() <<
"MAPI unsupported: tag=" <<
Qt::hex << mapi.tag <<
", type=" << mapi.type;
897 clearMAPIValue(mapi);
902 case MAPI_TAG_DATA: {
903 if (mapi.type == MAPI_TYPE_OBJECT && attach) {
905 int len = data.
size();
907 device_->seek(device_->pos() - len);
908 quint32 interface_ID;
909 stream_ >> interface_ID;
910 if (interface_ID == MAPI_IID_IMessage) {
915 attach->
setMimeTag(QStringLiteral(
"application/vnd.ms-tnef"));
917 qCDebug(KTNEF_LOG) <<
"MAPI Embedded Message: size=" << data.
size();
919 device_->seek(device_->pos() + (len - 4));
921 }
else if (mapi.type == MAPI_TYPE_BINARY && attach && attach->
offset() < 0) {
922 foundAttachment =
true;
923 int len = mapi.value.toByteArray().size();
925 attach->setSize(len);
926 attach->setOffset(device_->pos() - len);
927 attach->addAttribute(attATTACHDATA, atpBYTE, QStringLiteral("< size=%1 >").arg(len), false);
930 qCDebug(KTNEF_LOG) << "MAPI data: size=" << mapi.value.toByteArray().size();
934 if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
935 if (mapi.name.type == 0) {
938 mapiname = QStringLiteral(
" [name = %1]").
arg(mapi.name.value.toString());
941 switch (mapi.type & 0x0FFF) {
942 case MAPI_TYPE_UINT16:
943 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI short" << mapiname.
toLatin1().
data() <<
":" <<
Qt::hex
944 << mapi.value.toUInt();
946 case MAPI_TYPE_ULONG:
947 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI long" << mapiname.
toLatin1().
data() <<
":" <<
Qt::hex
948 << mapi.value.toUInt();
950 case MAPI_TYPE_BOOLEAN:
951 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI boolean" << mapiname.
toLatin1().
data() <<
":" << mapi.value.toBool();
954 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI time" << mapiname.
toLatin1().
data() <<
":"
955 << mapi.value.toString().toLatin1().data();
957 case MAPI_TYPE_USTRING:
958 case MAPI_TYPE_STRING8:
959 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI string" << mapiname.
toLatin1().
data()
960 <<
":size=" << mapi.value.toByteArray().size() << mapi.value.toString();
962 case MAPI_TYPE_BINARY:
963 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI binary" << mapiname.
toLatin1().
data()
964 <<
":size=" << mapi.value.toByteArray().size();
971 p =
new KTNEFProperty(key, (mapi.type & 0x0FFF), mapi.value, mapi.name.value);
977 if (foundAttachment && attach) {
Represents a TNEF attachment.
void setFileName(const QString &str)
Sets the filename of this attachment to str.
int size() const
Returns the size of the attachment.
void setSize(int size)
Sets the size of the attachment to size.
void setExtension(const QString &str)
Sets the filename extension of this attachment to str.
void setIndex(int indx)
Sets the index of this attachment to indx.
void setOffset(int offset)
Sets the offset value of this attachment to offset.
void setMimeTag(const QString &str)
Sets the MIME tag of this attachment to str.
QString name() const
Returns the name of the attachment.
void unsetDataParser()
Unsets the DataParsed flag for this attachment.
QString fileName() const
Returns the filename of the attachment.
void setName(const QString &str)
Sets the name of this attachment to str.
int offset() const
Returns the offset value of the attachment.
void setDisplaySize(int size)
Sets the display size of the attachment to size.
void setDisplayName(const QString &str)
Sets the display name of this attachment to str.
Represents a TNEF message.
bool extractFileTo(const QString &filename, const QString &dirname) const
Extracts a TNEF attachment having filename filename into the directory dirname.
bool openFile(const QString &filename) const
Opens the filename for parsing.
KTNEFParser()
Constructs a TNEF parser object.
void setDefaultExtractDir(const QString &dirname)
Sets the default extraction directory to dirname.
bool extractAll()
Extracts all TNEF attachments into the default directory.
bool extractFile(const QString &filename) const
Extracts a TNEF attachment having filename filename into the default directory.
bool openDevice(QIODevice *device)
Opens the QIODevice device for parsing.
~KTNEFParser()
Destroys the TNEF parser object.
KTNEFMessage * message() const
Returns the KTNEFMessage used in the parsing process.
QVariant property(int key) const
Returns the property associated with the specified key.
Interface for setting MAPI properties.
int key() const
Returns the integer key of the property.
This file is part of the API for handling TNEF data and defines the KTNEFAttach class.
This file is part of the API for handling TNEF data and provides some basic definitions for general u...
This file is part of the API for handling TNEF data and defines the KTNEFMessage class.
This file is part of the API for handling TNEF data and defines the KTNEFParser class.
This file is part of the API for handling TNEF data and defines the KTNEFProperty class.
Type type(const QSqlDatabase &db)
KIOCORE_EXPORT MimetypeJob * mimetype(const QUrl &url, JobFlags flags=DefaultFlags)
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
qsizetype size() const const
int readRawData(char *s, int len)
QDateTime fromSecsSinceEpoch(qint64 secs)
qsizetype size() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
void reserve(qsizetype size)
T value(qsizetype i) const const
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator find(const Key &key)
T value(const Key &key, const T &defaultValue) const const
QMimeType mimeTypeForData(QIODevice *device) const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
QString asprintf(const char *cformat,...)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString fromUtf16(const char16_t *unicode, qsizetype size)
bool isEmpty() const const
QByteArray toLatin1() const const
QString trimmed() const const
QTextStream & dec(QTextStream &stream)
QTextStream & hex(QTextStream &stream)
QTextStream & showbase(QTextStream &stream)
void setValue(QVariant &&value)
QString toString() const const
uint toUInt(bool *ok) const const