26#include "ktnef_debug.h"
27#include <QMimeDatabase>
37#include <QStandardPaths>
55void clearMAPIName(MAPI_value &mapi);
56void clearMAPIValue(MAPI_value &mapi,
bool clearName =
true);
57QString readMAPIString(
QDataStream &stream,
bool isUnicode =
false,
bool align =
true,
int len = -1);
58quint16 readMAPIValue(
QDataStream &stream, MAPI_value &mapi);
63QDateTime formatTime(quint32 lowB, quint32 highB);
74class KTnef::KTNEFParser::ParserPrivate
87 bool decodeAttachment();
90 void checkCurrent(
int key);
100 bool deleteDevice_ =
false;
105 : d(new ParserPrivate)
119void KTNEFParser::ParserPrivate::deleteDevice()
125 deleteDevice_ =
false;
128bool KTNEFParser::ParserPrivate::decodeMessage()
141 tag = (i1 & 0x0000FFFF);
142 type = ((i1 & 0xFFFF0000) >> 16);
146 off = device_->pos() + i2;
152 message_->addProperty(0x0062, MAPI_TYPE_ULONG, value);
153 qCDebug(KTNEF_LOG) <<
"Message Owner Appointment ID"
154 <<
"(length=" << i2 <<
")";
159 message_->addProperty(0x0063, MAPI_TYPE_UINT16, u);
161 qCDebug(KTNEF_LOG) <<
"Message Request Response"
162 <<
"(length=" << i2 <<
")";
165 value = readTNEFDate(stream_);
166 message_->addProperty(0x0E06, MAPI_TYPE_TIME, value);
167 qCDebug(KTNEF_LOG) <<
"Message Receive Date"
168 <<
"(length=" << i2 <<
")";
171 value = readMAPIString(stream_,
false,
false, i2);
172 message_->addProperty(0x001A, MAPI_TYPE_STRING8, value);
173 qCDebug(KTNEF_LOG) <<
"Message Class"
174 <<
"(length=" << i2 <<
")";
178 message_->addProperty(0x0026, MAPI_TYPE_ULONG, 2 - u);
180 qCDebug(KTNEF_LOG) <<
"Message Priority"
181 <<
"(length=" << i2 <<
")";
184 qCDebug(KTNEF_LOG) <<
"Message MAPI Properties"
185 <<
"(length=" << i2 <<
")";
187 int nProps = message_->properties().count();
188 i2 += device_->pos();
189 readMAPIProperties(message_->properties(),
nullptr);
191 qCDebug(KTNEF_LOG) <<
"Properties:" << message_->properties().count();
192 value = QStringLiteral(
"< %1 properties >").arg(message_->properties().count() - nProps);
195 case attTNEFVERSION: {
199 qCDebug(KTNEF_LOG) <<
"Message TNEF Version"
200 <<
"(length=" << i2 <<
")";
203 message_->addProperty(0x0024, MAPI_TYPE_STRING8, readTNEFAddress(stream_));
204 device_->seek(device_->pos() - i2);
205 value = readTNEFData(stream_, i2);
206 qCDebug(KTNEF_LOG) <<
"Message From"
207 <<
"(length=" << i2 <<
")";
210 value = readMAPIString(stream_,
false,
false, i2);
211 message_->addProperty(0x0037, MAPI_TYPE_STRING8, value);
212 qCDebug(KTNEF_LOG) <<
"Message Subject"
213 <<
"(length=" << i2 <<
")";
216 value = readTNEFDate(stream_);
217 message_->addProperty(0x0039, MAPI_TYPE_TIME, value);
218 qCDebug(KTNEF_LOG) <<
"Message Date Sent"
219 <<
"(length=" << i2 <<
")";
226 flag |= MSGFLAG_READ;
228 if (!(c & fmsModified)) {
229 flag |= MSGFLAG_UNMODIFIED;
231 if (c & fmsSubmitted) {
232 flag |= MSGFLAG_SUBMIT;
234 if (c & fmsHasAttach) {
235 flag |= MSGFLAG_HASATTACH;
238 flag |= MSGFLAG_UNSENT;
240 message_->addProperty(0x0E07, MAPI_TYPE_ULONG, flag);
243 qCDebug(KTNEF_LOG) <<
"Message Status"
244 <<
"(length=" << i2 <<
")";
246 case attRECIPTABLE: {
250 if (rows > (INT_MAX /
sizeof(
QVariant))) {
254 for (uint i = 0; i < rows; i++) {
256 readMAPIProperties(props,
nullptr);
257 recipTable << formatRecipient(props);
259 message_->addProperty(0x0E12, MAPI_TYPE_STRING8, recipTable);
260 device_->seek(device_->pos() - i2);
261 value = readTNEFData(stream_, i2);
263 qCDebug(KTNEF_LOG) <<
"Message Recipient Table"
264 <<
"(length=" << i2 <<
")";
267 value = readMAPIString(stream_,
false,
false, i2);
268 message_->addProperty(0x1000, MAPI_TYPE_STRING8, value);
269 qCDebug(KTNEF_LOG) <<
"Message Body"
270 <<
"(length=" << i2 <<
")";
272 case attDATEMODIFIED:
273 value = readTNEFDate(stream_);
274 message_->addProperty(0x3008, MAPI_TYPE_TIME, value);
275 qCDebug(KTNEF_LOG) <<
"Message Date Modified"
276 <<
"(length=" << i2 <<
")";
279 value = readMAPIString(stream_,
false,
false, i2);
280 message_->addProperty(0x300B, MAPI_TYPE_STRING8, value);
281 qCDebug(KTNEF_LOG) <<
"Message ID"
282 <<
"(length=" << i2 <<
")";
285 value = readTNEFData(stream_, i2);
286 qCDebug(KTNEF_LOG) <<
"Message OEM Code Page"
287 <<
"(length=" << i2 <<
")";
290 value = readTNEFAttribute(stream_, type, i2);
295 if (device_->pos() != off && !device_->seek(off)) {
301 message_->addAttribute(tag, type, value,
true);
306bool KTNEFParser::ParserPrivate::decodeAttachment()
316 tag = (i & 0x0000FFFF);
317 type = ((i & 0xFFFF0000) >> 16);
322 value = readMAPIString(stream_,
false,
false, i);
323 current_->setName(value.
toString());
324 qCDebug(KTNEF_LOG) <<
"Attachment Title:" << current_->name();
327 current_->setSize(i);
328 current_->setOffset(device_->pos());
329 device_->seek(device_->pos() + i);
330 value = QStringLiteral(
"< size=%1 >").arg(i);
331 qCDebug(KTNEF_LOG) <<
"Attachment Data: size=" << i;
335 readMAPIProperties(current_->properties(), current_);
337 current_->setIndex(current_->property(MAPI_TAG_INDEX).toUInt());
338 current_->setDisplaySize(current_->property(MAPI_TAG_SIZE).toUInt());
339 str = current_->property(MAPI_TAG_DISPLAYNAME).toString();
341 current_->setDisplayName(str);
343 current_->setFileName(current_->property(MAPI_TAG_FILENAME).toString());
344 str = current_->property(MAPI_TAG_MIMETAG).toString();
346 current_->setMimeTag(str);
348 current_->setExtension(current_->property(MAPI_TAG_EXTENSION).toString());
349 value = QStringLiteral(
"< %1 properties >").arg(current_->properties().count());
351 case attATTACHMODDATE:
352 value = readTNEFDate(stream_);
353 qCDebug(KTNEF_LOG) <<
"Attachment Modification Date:" << value.
toString();
355 case attATTACHCREATEDATE:
356 value = readTNEFDate(stream_);
357 qCDebug(KTNEF_LOG) <<
"Attachment Creation Date:" << value.
toString();
359 case attATTACHMETAFILE:
360 qCDebug(KTNEF_LOG) <<
"Attachment Metafile: size=" << i;
363 value = readTNEFData(stream_, i);
366 value = readTNEFAttribute(stream_, type, i);
367 qCDebug(KTNEF_LOG) <<
"Attachment unknown field: tag=" <<
Qt::hex << tag <<
", length=" <<
Qt::dec << i;
372 current_->addAttribute(tag, type, value,
true);
380 d->defaultdir_ = dirname;
383bool KTNEFParser::ParserPrivate::parseDevice()
389 message_->clearAttachments();
393 if (!device_->isOpen()) {
395 qCDebug(KTNEF_LOG) <<
"Couldn't open device";
399 if (!device_->isReadable()) {
400 qCDebug(KTNEF_LOG) <<
"Device not readable";
404 stream_.setDevice(device_);
407 if (i == TNEF_SIGNATURE) {
409 qCDebug(KTNEF_LOG).nospace() <<
"Attachment cross reference key: 0x" <<
Qt::hex << qSetFieldWidth(4) << qSetPadChar(
QLatin1Char(
'0')) << u;
411 while (!stream_.atEnd()) {
415 if (!decodeMessage()) {
420 if (!decodeAttachment()) {
425 qCDebug(KTNEF_LOG) <<
"Unknown Level:" << c <<
", at offset" << device_->pos();
430 checkCurrent(attATTACHDATA);
438 qCDebug(KTNEF_LOG) <<
"This is not a TNEF file";
447 KTNEFAttach *att = d->message_->attachment(filename);
451 return d->extractAttachmentTo(att, d->defaultdir_);
454bool KTNEFParser::ParserPrivate::extractAttachmentTo(
KTNEFAttach *att,
const QString &dirname)
456 const QString destDir(
QDir(dirname).absolutePath());
462 filename += att->
name();
468 if (!device_->isOpen()) {
471 if (!device_->seek(att->
offset())) {
476 if (!fi.absoluteFilePath().startsWith(destDir)) {
477 qCWarning(KTNEF_LOG) <<
"Attempted extract into" << fi.absoluteFilePath() <<
"which is outside of the extraction root folder" << destDir <<
"."
478 <<
"Changing export of contained files to extraction root folder.";
479 filename = destDir +
QLatin1Char(
'/') + fi.fileName();
487 quint32 len = att->
size();
489 char *buf =
new char[sz];
491 while (ok && len > 0) {
492 const int n = device_->read(buf, qMin(sz, len));
497 if (outfile.write(buf, n) != n) {
513 for (; it != itEnd; ++it) {
514 if (!d->extractAttachmentTo(*it, d->defaultdir_)) {
523 qCDebug(KTNEF_LOG) <<
"Extracting attachment: filename=" << filename <<
", dir=" << dirname;
524 KTNEFAttach *att = d->message_->attachment(filename);
528 return d->extractAttachmentTo(att, dirname);
536 auto file =
new QFile(filename);
538 d->deleteDevice_ =
true;
539 if (!file->exists()) {
542 return d->parseDevice();
549 return d->parseDevice();
552void KTNEFParser::ParserPrivate::checkCurrent(
int key)
557 if (current_->attributes().contains(key)) {
558 if (current_->offset() >= 0) {
559 if (current_->name().isEmpty()) {
560 current_->setName(QStringLiteral(
"Unnamed"));
562 if (current_->mimeTag().isEmpty()) {
568 if (!current_->fileName().isEmpty()) {
574 if (
mimetype.name() ==
"application/octet-stream"_L1 && current_->size() > 0) {
575 qint64 oldOffset = device_->pos();
576 QByteArray buffer(qMin(32, current_->size()),
'\0');
577 device_->seek(current_->offset());
578 device_->read(buffer.data(), buffer.size());
580 device_->seek(oldOffset);
582 current_->setMimeTag(
mimetype.name());
584 message_->addAttachment(current_);
601 n = (n + b) & ~(b - 1); \
603#define ISVECTOR(m) (((m).type & 0xF000) == MAPI_TYPE_VECTOR)
605void clearMAPIName(MAPI_value &mapi)
607 mapi.name.value.clear();
610void clearMAPIValue(MAPI_value &mapi,
bool clearName)
618QDateTime formatTime(quint32 lowB, quint32 highB)
625 u64 -= 116444736000000000LL;
627 if (u64 <= 0xffffffffU) {
630 qCWarning(KTNEF_LOG).nospace() <<
"Invalid date: low byte=" <<
Qt::showbase << qSetFieldWidth(8) << qSetPadChar(
QLatin1Char(
'0')) << lowB
631 <<
", high byte=" << highB;
643 if ((it = props.
find(0x3001)) != props.
end()) {
644 dn = (*it)->valueString();
646 if ((it = props.
find(0x3003)) != props.
end()) {
647 addr = (*it)->valueString();
649 if ((it = props.
find(0x0C15)) != props.
end()) {
650 switch ((*it)->value().toInt()) {
652 t = QStringLiteral(
"From:");
655 t = QStringLiteral(
"To:");
658 t = QStringLiteral(
"Cc:");
661 t = QStringLiteral(
"Bcc:");
671 if (!addr.
isEmpty() && addr != dn) {
688 stream >> y >> m >> d >> hh >> mm >> ss >> dm;
698 stream >> totalLen >> totalLen >> strLen >> addrLen;
699 s.
append(readMAPIString(stream,
false,
false, strLen));
701 s.
append(readMAPIString(stream,
false,
false, addrLen));
704 for (
int i = 8 + strLen + addrLen; i < totalLen; i++) {
724 return readMAPIString(stream,
false,
false, len);
726 return readTNEFDate(stream);
728 return readTNEFData(stream, len);
745 quint32 fullLen = len;
752 for (uint i = len; i < fullLen; i++) {
765quint16 readMAPIValue(
QDataStream &stream, MAPI_value &mapi)
769 clearMAPIValue(mapi);
771 mapi.type = (d & 0x0000FFFF);
772 mapi.tag = ((d & 0xFFFF0000) >> 16);
773 if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
775 stream >> d >> d >> d >> d;
777 stream >> mapi.name.type;
779 if (mapi.name.type == 0) {
782 mapi.name.value.setValue(tmp);
783 }
else if (mapi.name.type == 1) {
784 mapi.name.value.setValue(readMAPIString(stream,
true));
790 if (ISVECTOR(mapi)) {
794 for (
int i = 0; i < n; i++) {
796 switch (mapi.type & 0x0FFF) {
797 case MAPI_TYPE_UINT16:
801 case MAPI_TYPE_BOOLEAN:
802 case MAPI_TYPE_ULONG: {
807 case MAPI_TYPE_FLOAT:
811 case MAPI_TYPE_DOUBLE: {
816 case MAPI_TYPE_TIME: {
819 stream >> lowB >> highB;
820 value = formatTime(lowB, highB);
822 case MAPI_TYPE_USTRING:
823 case MAPI_TYPE_STRING8:
826 if (ISVECTOR(mapi)) {
831 for (uint j = 0; j < d; j++) {
833 value.
setValue(readMAPIString(stream, (mapi.type & 0x0FFF) == MAPI_TYPE_USTRING));
836 case MAPI_TYPE_OBJECT:
837 case MAPI_TYPE_BINARY:
838 if (ISVECTOR(mapi)) {
843 for (uint i = 0; i < d && !stream.
atEnd(); i++) {
848 if (len > 0 && len <= INT_MAX) {
851 stream.readRawData(value.toByteArray().data(), len);
853 for (uint i = len; i < fullLen; i++) {
861 mapi.type = MAPI_TYPE_NONE;
864 if (ISVECTOR(mapi)) {
867 mapi.
value.setValue(lst);
882 bool foundAttachment =
false;
885 mapi.type = MAPI_TYPE_NONE;
890 qCDebug(KTNEF_LOG) <<
"MAPI Properties:" << n;
891 for (uint i = 0; i < n; i++) {
892 if (stream_.atEnd()) {
893 clearMAPIValue(mapi);
896 readMAPIValue(stream_, mapi);
897 if (mapi.type == MAPI_TYPE_NONE) {
898 qCDebug(KTNEF_LOG).nospace() <<
"MAPI unsupported: tag=" <<
Qt::hex << mapi.tag <<
", type=" << mapi.type;
899 clearMAPIValue(mapi);
904 case MAPI_TAG_DATA: {
905 if (mapi.type == MAPI_TYPE_OBJECT && attach) {
907 int len = data.
size();
909 device_->seek(device_->pos() - len);
910 quint32 interface_ID;
911 stream_ >> interface_ID;
912 if (interface_ID == MAPI_IID_IMessage) {
917 attach->
setMimeTag(QStringLiteral(
"application/vnd.ms-tnef"));
919 qCDebug(KTNEF_LOG) <<
"MAPI Embedded Message: size=" << data.
size();
921 device_->seek(device_->pos() + (len - 4));
923 }
else if (mapi.type == MAPI_TYPE_BINARY && attach && attach->
offset() < 0) {
924 foundAttachment =
true;
925 int len = mapi.value.toByteArray().size();
927 attach->setSize(len);
928 attach->setOffset(device_->pos() - len);
929 attach->addAttribute(attATTACHDATA, atpBYTE, QStringLiteral("< size=%1 >").arg(len), false);
932 qCDebug(KTNEF_LOG) << "MAPI data: size=" << mapi.value.toByteArray().size();
936 if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
937 if (mapi.name.type == 0) {
938 mapiname = QString::asprintf(
" [name = 0x%04x]", mapi.name.value.toUInt());
940 mapiname = QStringLiteral(
" [name = %1]").arg(mapi.name.value.toString());
943 switch (mapi.type & 0x0FFF) {
944 case MAPI_TYPE_UINT16:
945 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI short" << mapiname.
toLatin1().
data() <<
":" <<
Qt::hex
946 << mapi.value.toUInt();
948 case MAPI_TYPE_ULONG:
949 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI long" << mapiname.
toLatin1().
data() <<
":" <<
Qt::hex
950 << mapi.value.toUInt();
952 case MAPI_TYPE_BOOLEAN:
953 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI boolean" << mapiname.
toLatin1().
data() <<
":" << mapi.value.toBool();
956 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI time" << mapiname.
toLatin1().
data() <<
":"
957 << mapi.value.toString().toLatin1().data();
959 case MAPI_TYPE_USTRING:
960 case MAPI_TYPE_STRING8:
961 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI string" << mapiname.
toLatin1().
data()
962 <<
":size=" << mapi.value.toByteArray().size() << mapi.value.toString();
964 case MAPI_TYPE_BINARY:
965 qCDebug(KTNEF_LOG).nospace() <<
"(tag=" <<
Qt::hex << mapi.tag <<
") MAPI binary" << mapiname.
toLatin1().
data()
966 <<
":size=" << mapi.value.toByteArray().size();
973 p =
new KTNEFProperty(key, (mapi.type & 0x0FFF), mapi.value, mapi.name.value);
979 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)
QString name(StandardAction id)
const QList< QKeySequence > & end()
qsizetype size() const const
int readRawData(char *s, int len)
QDateTime fromSecsSinceEpoch(qint64 secs)
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)
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