KTnef

ktnefparser.cpp
Go to the documentation of this file.
1 /*
2  ktnefparser.cpp
3 
4  SPDX-FileCopyrightText: 2002 Michael Goffioul <[email protected]>
5 
6  This file is part of KTNEF, the KDE TNEF support library/program.
7 
8  SPDX-License-Identifier: LGPL-2.0-or-later
9  */
18 #include "ktnefparser.h"
19 #include "ktnefattach.h"
20 #include "ktnefproperty.h"
21 #include "ktnefmessage.h"
22 #include "ktnefdefs.h"
23 
24 #include "ktnef_debug.h"
25 #include <QMimeType>
26 #include <QMimeDatabase>
27 #include <QSaveFile>
28 
29 #include <QDateTime>
30 #include <QDataStream>
31 #include <QDir>
32 #include <QFile>
33 #include <QFileInfo>
34 #include <QVariant>
35 #include <QList>
36 #include <QStandardPaths>
37 
38 using namespace KTnef;
39 
40 //@cond PRIVATE
41 typedef struct {
42  quint16 type;
43  quint16 tag;
44  QVariant value;
45  struct {
46  quint32 type;
47  QVariant value;
48  } name;
49 } MAPI_value;
50 //@endcond
51 
52 //@cond IGNORE
53 void clearMAPIName(MAPI_value &mapi);
54 void clearMAPIValue(MAPI_value &mapi, bool clearName = true);
55 QString readMAPIString(QDataStream &stream, bool isUnicode = false,
56  bool align = true, int len = -1);
57 quint16 readMAPIValue(QDataStream &stream, MAPI_value &mapi);
58 QDateTime readTNEFDate(QDataStream &stream);
59 QString readTNEFAddress(QDataStream &stream);
60 QByteArray readTNEFData(QDataStream &stream, quint32 len);
61 QVariant readTNEFAttribute(QDataStream &stream, quint16 type, quint32 len);
62 QDateTime formatTime(quint32 lowB, quint32 highB);
63 QString formatRecipient(const QMap<int, KTnef::KTNEFProperty *> &props);
64 //@endcond
65 
66 //------------------------------------------------------------------------------
67 
72 //@cond PRIVATE
73 class KTnef::KTNEFParser::ParserPrivate
74 {
75 public:
76  ParserPrivate()
77  {
79  message_ = new KTNEFMessage;
80  }
81  ~ParserPrivate()
82  {
83  delete message_;
84  }
85 
86  bool decodeAttachment();
87  bool decodeMessage();
88  bool extractAttachmentTo(KTNEFAttach *att, const QString &dirname);
89  void checkCurrent(int key);
90  bool readMAPIProperties(QMap<int, KTNEFProperty *> &props,
91  KTNEFAttach *attach = nullptr);
92  bool parseDevice();
93  void deleteDevice();
94 
95  QString defaultdir_;
96  QDataStream stream_;
97  QIODevice *device_ = nullptr;
98  KTNEFAttach *current_ = nullptr;
99  KTNEFMessage *message_ = nullptr;
100  bool deleteDevice_ = false;
101 };
102 //@endcond
103 
105  : d(new ParserPrivate)
106 {
107 }
108 
110 {
111  d->deleteDevice();
112  delete d;
113 }
114 
116 {
117  return d->message_;
118 }
119 
120 void KTNEFParser::ParserPrivate::deleteDevice()
121 {
122  if (deleteDevice_) {
123  delete device_;
124  }
125  device_ = nullptr;
126  deleteDevice_ = false;
127 }
128 
129 bool KTNEFParser::ParserPrivate::decodeMessage()
130 {
131  quint32 i1, i2, off;
132  quint16 u, tag, type;
133  QVariant value;
134 
135  // read (type+name)
136  stream_ >> i1;
137  u = 0;
138  tag = (i1 & 0x0000FFFF);
139  type = ((i1 & 0xFFFF0000) >> 16);
140  // read data length
141  stream_ >> i2;
142  // offset after reading the value
143  off = device_->pos() + i2;
144  switch (tag) {
145  case attAIDOWNER: {
146  uint tmp;
147  stream_ >> tmp;
148  value.setValue(tmp);
149  message_->addProperty(0x0062, MAPI_TYPE_ULONG, value);
150  qCDebug(KTNEF_LOG) << "Message Owner Appointment ID" << "(length=" << i2 << ")";
151  break;
152  }
153  case attREQUESTRES:
154  stream_ >> u;
155  message_->addProperty(0x0063, MAPI_TYPE_UINT16, u);
156  value = (bool)u;
157  qCDebug(KTNEF_LOG) << "Message Request Response" << "(length=" << i2 << ")";
158  break;
159  case attDATERECD:
160  value = readTNEFDate(stream_);
161  message_->addProperty(0x0E06, MAPI_TYPE_TIME, value);
162  qCDebug(KTNEF_LOG) << "Message Receive Date" << "(length=" << i2 << ")";
163  break;
164  case attMSGCLASS:
165  value = readMAPIString(stream_, false, false, i2);
166  message_->addProperty(0x001A, MAPI_TYPE_STRING8, value);
167  qCDebug(KTNEF_LOG) << "Message Class" << "(length=" << i2 << ")";
168  break;
169  case attMSGPRIORITY:
170  stream_ >> u;
171  message_->addProperty(0x0026, MAPI_TYPE_ULONG, 2 - u);
172  value = u;
173  qCDebug(KTNEF_LOG) << "Message Priority" << "(length=" << i2 << ")";
174  break;
175  case attMAPIPROPS:
176  qCDebug(KTNEF_LOG) << "Message MAPI Properties" << "(length=" << i2 << ")";
177  {
178  int nProps = message_->properties().count();
179  i2 += device_->pos();
180  readMAPIProperties(message_->properties(), nullptr);
181  device_->seek(i2);
182  qCDebug(KTNEF_LOG) << "Properties:" << message_->properties().count();
183  value = QStringLiteral("< %1 properties >").
184  arg(message_->properties().count() - nProps);
185  }
186  break;
187  case attTNEFVERSION: {
188  uint tmp;
189  stream_ >> tmp;
190  value.setValue(tmp);
191  qCDebug(KTNEF_LOG) << "Message TNEF Version" << "(length=" << i2 << ")";
192  }
193  break;
194  case attFROM:
195  message_->addProperty(0x0024, MAPI_TYPE_STRING8, readTNEFAddress(stream_));
196  device_->seek(device_->pos() - i2);
197  value = readTNEFData(stream_, i2);
198  qCDebug(KTNEF_LOG) << "Message From" << "(length=" << i2 << ")";
199  break;
200  case attSUBJECT:
201  value = readMAPIString(stream_, false, false, i2);
202  message_->addProperty(0x0037, MAPI_TYPE_STRING8, value);
203  qCDebug(KTNEF_LOG) << "Message Subject" << "(length=" << i2 << ")";
204  break;
205  case attDATESENT:
206  value = readTNEFDate(stream_);
207  message_->addProperty(0x0039, MAPI_TYPE_TIME, value);
208  qCDebug(KTNEF_LOG) << "Message Date Sent" << "(length=" << i2 << ")";
209  break;
210  case attMSGSTATUS: {
211  quint8 c;
212  quint32 flag = 0;
213  stream_ >> c;
214  if (c & fmsRead) {
215  flag |= MSGFLAG_READ;
216  }
217  if (!(c & fmsModified)) {
218  flag |= MSGFLAG_UNMODIFIED;
219  }
220  if (c & fmsSubmitted) {
221  flag |= MSGFLAG_SUBMIT;
222  }
223  if (c & fmsHasAttach) {
224  flag |= MSGFLAG_HASATTACH;
225  }
226  if (c & fmsLocal) {
227  flag |= MSGFLAG_UNSENT;
228  }
229  message_->addProperty(0x0E07, MAPI_TYPE_ULONG, flag);
230  value = c;
231  }
232  qCDebug(KTNEF_LOG) << "Message Status" << "(length=" << i2 << ")";
233  break;
234  case attRECIPTABLE: {
235  quint32 rows;
236  QList<QVariant> recipTable;
237  stream_ >> rows;
238  if (rows > (INT_MAX / sizeof(QVariant)))
239  return false;
240  recipTable.reserve(rows);
241  for (uint i = 0; i < rows; i++) {
243  readMAPIProperties(props, nullptr);
244  recipTable << formatRecipient(props);
245  }
246  message_->addProperty(0x0E12, MAPI_TYPE_STRING8, recipTable);
247  device_->seek(device_->pos() - i2);
248  value = readTNEFData(stream_, i2);
249  }
250  qCDebug(KTNEF_LOG) << "Message Recipient Table" << "(length=" << i2 << ")";
251  break;
252  case attBODY:
253  value = readMAPIString(stream_, false, false, i2);
254  message_->addProperty(0x1000, MAPI_TYPE_STRING8, value);
255  qCDebug(KTNEF_LOG) << "Message Body" << "(length=" << i2 << ")";
256  break;
257  case attDATEMODIFIED:
258  value = readTNEFDate(stream_);
259  message_->addProperty(0x3008, MAPI_TYPE_TIME, value);
260  qCDebug(KTNEF_LOG) << "Message Date Modified" << "(length=" << i2 << ")";
261  break;
262  case attMSGID:
263  value = readMAPIString(stream_, false, false, i2);
264  message_->addProperty(0x300B, MAPI_TYPE_STRING8, value);
265  qCDebug(KTNEF_LOG) << "Message ID" << "(length=" << i2 << ")";
266  break;
267  case attOEMCODEPAGE:
268  value = readTNEFData(stream_, i2);
269  qCDebug(KTNEF_LOG) << "Message OEM Code Page" << "(length=" << i2 << ")";
270  break;
271  default:
272  value = readTNEFAttribute(stream_, type, i2);
273  //qCDebug(KTNEF_LOG).form( "Message: type=%x, length=%d, check=%x\n", i1, i2, u );
274  break;
275  }
276  // skip data
277  if (device_->pos() != off && !device_->seek(off)) {
278  return false;
279  }
280  // get checksum
281  stream_ >> u;
282  // add TNEF attribute
283  message_->addAttribute(tag, type, value, true);
284  //qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
285  return true;
286 }
287 
288 bool KTNEFParser::ParserPrivate::decodeAttachment()
289 {
290  quint32 i;
291  quint16 tag, type, u;
292  QVariant value;
293  QString str;
294 
295  stream_ >> i; // i <- attribute type & name
296  tag = (i & 0x0000FFFF);
297  type = ((i & 0xFFFF0000) >> 16);
298  stream_ >> i; // i <- data length
299  checkCurrent(tag);
300  switch (tag) {
301  case attATTACHTITLE:
302  value = readMAPIString(stream_, false, false, i);
303  current_->setName(value.toString());
304  qCDebug(KTNEF_LOG) << "Attachment Title:" << current_->name();
305  break;
306  case attATTACHDATA:
307  current_->setSize(i);
308  current_->setOffset(device_->pos());
309  device_->seek(device_->pos() + i);
310  value = QStringLiteral("< size=%1 >").arg(i);
311  qCDebug(KTNEF_LOG) << "Attachment Data: size=" << i;
312  break;
313  case attATTACHMENT: // try to get attachment info
314  i += device_->pos();
315  readMAPIProperties(current_->properties(), current_);
316  device_->seek(i);
317  current_->setIndex(current_->property(MAPI_TAG_INDEX).toUInt());
318  current_->setDisplaySize(current_->property(MAPI_TAG_SIZE).toUInt());
319  str = current_->property(MAPI_TAG_DISPLAYNAME).toString();
320  if (!str.isEmpty()) {
321  current_->setDisplayName(str);
322  }
323  current_->setFileName(current_->property(MAPI_TAG_FILENAME).
324  toString());
325  str = current_->property(MAPI_TAG_MIMETAG).toString();
326  if (!str.isEmpty()) {
327  current_->setMimeTag(str);
328  }
329  current_->setExtension(current_->property(MAPI_TAG_EXTENSION).
330  toString());
331  value = QStringLiteral("< %1 properties >").
332  arg(current_->properties().count());
333  break;
334  case attATTACHMODDATE:
335  value = readTNEFDate(stream_);
336  qCDebug(KTNEF_LOG) << "Attachment Modification Date:" << value.toString();
337  break;
338  case attATTACHCREATEDATE:
339  value = readTNEFDate(stream_);
340  qCDebug(KTNEF_LOG) << "Attachment Creation Date:" << value.toString();
341  break;
342  case attATTACHMETAFILE:
343  qCDebug(KTNEF_LOG) << "Attachment Metafile: size=" << i;
344  //value = QString( "< size=%1 >" ).arg( i );
345  //device_->seek( device_->pos()+i );
346  value = readTNEFData(stream_, i);
347  break;
348  default:
349  value = readTNEFAttribute(stream_, type, i);
350  qCDebug(KTNEF_LOG) << "Attachment unknown field: tag="
351  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
352  << hex
353  #else
354  << Qt::hex
355  #endif
356  << tag << ", length="
357  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
358  << dec
359  #else
360  << Qt::dec
361  #endif
362  << i;
363  break;
364  }
365  stream_ >> u; // u <- checksum
366  // add TNEF attribute
367  current_->addAttribute(tag, type, value, true);
368  //qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
369 
370  return true;
371 }
372 
374 {
375  d->defaultdir_ = dirname;
376 }
377 
378 bool KTNEFParser::ParserPrivate::parseDevice()
379 {
380  quint16 u;
381  quint32 i;
382  quint8 c;
383 
384  message_->clearAttachments();
385  delete current_;
386  current_ = nullptr;
387 
388  if (!device_->isOpen()) {
389  if (!device_->open(QIODevice::ReadOnly)) {
390  qCDebug(KTNEF_LOG) << "Couldn't open device";
391  return false;
392  }
393  }
394  if (!device_->isReadable()) {
395  qCDebug(KTNEF_LOG) << "Device not readable";
396  return false;
397  }
398 
399  stream_.setDevice(device_);
400  stream_.setByteOrder(QDataStream::LittleEndian);
401  stream_ >> i;
402  if (i == TNEF_SIGNATURE) {
403  stream_ >> u;
404  qCDebug(KTNEF_LOG).nospace() << "Attachment cross reference key: 0x"
405  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
406  << hex
407  #else
408  << Qt::hex
409  #endif
410  << qSetFieldWidth(4) << qSetPadChar(QLatin1Char('0')) << u;
411  //qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
412  while (!stream_.atEnd()) {
413  stream_ >> c;
414  switch (c) {
415  case LVL_MESSAGE:
416  if (!decodeMessage()) {
417  goto end;
418  }
419  break;
420  case LVL_ATTACHMENT:
421  if (!decodeAttachment()) {
422  goto end;
423  }
424  break;
425  default:
426  qCDebug(KTNEF_LOG) << "Unknown Level:" << c << ", at offset" << device_->pos();
427  goto end;
428  }
429  }
430  if (current_) {
431  checkCurrent(attATTACHDATA); // this line has the effect to append the
432  // attachment, if it has data. If not it does
433  // nothing, and the attachment will be discarded
434  delete current_;
435  current_ = nullptr;
436  }
437  return true;
438  } else {
439  qCDebug(KTNEF_LOG) << "This is not a TNEF file";
440  end:
441  device_->close();
442  return false;
443  }
444 }
445 
446 bool KTNEFParser::extractFile(const QString &filename) const
447 {
448  KTNEFAttach *att = d->message_->attachment(filename);
449  if (!att) {
450  return false;
451  }
452  return d->extractAttachmentTo(att, d->defaultdir_);
453 }
454 
455 bool KTNEFParser::ParserPrivate::extractAttachmentTo(KTNEFAttach *att,
456  const QString &dirname)
457 {
458  const QString destDir(QDir(dirname).absolutePath()); // get directory path without any "." or ".."
459 
460  QString filename = destDir + QLatin1Char('/');
461  if (!att->fileName().isEmpty()) {
462  filename += att->fileName();
463  } else {
464  filename += att->name();
465  }
466  if (filename.endsWith(QLatin1Char('/'))) {
467  return false;
468  }
469 
470  if (!device_->isOpen()) {
471  return false;
472  }
473  if (!device_->seek(att->offset())) {
474  return false;
475  }
476 
477  const QFileInfo fi(filename);
478  if (!fi.absoluteFilePath().startsWith(destDir)) {
479  qWarning() << "Attempted extract into" << fi.absoluteFilePath()
480  << "which is outside of the extraction root folder" << destDir << "."
481  << "Changing export of contained files to extraction root folder.";
482  filename = destDir + QLatin1Char('/') + fi.fileName();
483  }
484 
485  QSaveFile outfile(filename);
486  if (!outfile.open(QIODevice::WriteOnly)) {
487  return false;
488  }
489 
490  quint32 len = att->size(), sz(16384);
491  int n(0);
492  char *buf = new char[sz];
493  bool ok(true);
494  while (ok && len > 0) {
495  n = device_->read(buf, qMin(sz, len));
496  if (n < 0) {
497  ok = false;
498  } else {
499  len -= n;
500  if (outfile.write(buf, n) != n) {
501  ok = false;
502  }
503  }
504  }
505  outfile.commit();
506  delete [] buf;
507 
508  return ok;
509 }
510 
512 {
513  QList<KTNEFAttach *> l = d->message_->attachmentList();
516  for (; it != itEnd ; ++it) {
517  if (!d->extractAttachmentTo(*it, d->defaultdir_)) {
518  return false;
519  }
520  }
521  return true;
522 }
523 
524 bool KTNEFParser::extractFileTo(const QString &filename,
525  const QString &dirname) const
526 {
527  qCDebug(KTNEF_LOG) << "Extracting attachment: filename="
528  << filename << ", dir=" << dirname;
529  KTNEFAttach *att = d->message_->attachment(filename);
530  if (!att) {
531  return false;
532  }
533  return d->extractAttachmentTo(att, dirname);
534 }
535 
536 bool KTNEFParser::openFile(const QString &filename) const
537 {
538  d->deleteDevice();
539  delete d->message_;
540  d->message_ = new KTNEFMessage();
541  QFile *file = new QFile(filename);
542  d->device_ = file;
543  d->deleteDevice_ = true;
544  if (!file->exists()) {
545  return false;
546  }
547  return d->parseDevice();
548 }
549 
551 {
552  d->deleteDevice();
553  d->device_ = device;
554  return d->parseDevice();
555 }
556 
557 void KTNEFParser::ParserPrivate::checkCurrent(int key)
558 {
559  if (!current_) {
560  current_ = new KTNEFAttach();
561  } else {
562  if (current_->attributes().contains(key)) {
563  if (current_->offset() >= 0) {
564  if (current_->name().isEmpty()) {
565  current_->setName(QStringLiteral("Unnamed"));
566  }
567  if (current_->mimeTag().isEmpty()) {
568  // No mime type defined in the TNEF structure,
569  // try to find it from the attachment filename
570  // and/or content (using at most 32 bytes)
571  QMimeType mimetype;
572  QMimeDatabase db;
573  if (!current_->fileName().isEmpty()) {
574  mimetype = db.mimeTypeForFile(current_->fileName(), QMimeDatabase::MatchExtension);
575  }
576  if (!mimetype.isValid()) {
577  return; // FIXME
578  }
579  if (mimetype.name() == QLatin1String("application/octet-stream") &&
580  current_->size() > 0) {
581  qint64 oldOffset = device_->pos();
582  QByteArray buffer(qMin(32, current_->size()), '\0');
583  device_->seek(current_->offset());
584  device_->read(buffer.data(), buffer.size());
585  mimetype = db.mimeTypeForData(buffer);
586  device_->seek(oldOffset);
587  }
588  current_->setMimeTag(mimetype.name());
589  }
590  message_->addAttachment(current_);
591  current_ = nullptr;
592  } else {
593  // invalid attachment, skip it
594  delete current_;
595  current_ = nullptr;
596  }
597  current_ = new KTNEFAttach();
598  }
599  }
600 }
601 
602 //------------------------------------------------------------------------------
603 
604 //@cond IGNORE
605 #define ALIGN( n, b ) if ( n & ( b-1 ) ) { n = ( n + b ) & ~( b-1 ); }
606 #define ISVECTOR( m ) ( ( ( m ).type & 0xF000 ) == MAPI_TYPE_VECTOR )
607 
608 void clearMAPIName(MAPI_value &mapi)
609 {
610  mapi.name.value.clear();
611 }
612 
613 void clearMAPIValue(MAPI_value &mapi, bool clearName)
614 {
615  mapi.value.clear();
616  if (clearName) {
617  clearMAPIName(mapi);
618  }
619 }
620 
621 QDateTime formatTime(quint32 lowB, quint32 highB)
622 {
623  QDateTime dt;
624  quint64 u64;
625  u64 = highB;
626  u64 <<= 32;
627  u64 |= lowB;
628  u64 -= 116444736000000000LL;
629  u64 /= 10000000;
630  if (u64 <= 0xffffffffU) {
631  dt = QDateTime::fromSecsSinceEpoch((unsigned int)u64);
632  } else {
633  qCWarning(KTNEF_LOG).nospace() << "Invalid date: low byte="
634  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
635  << showbase
636  #else
637  << Qt::showbase
638  #endif
639  << qSetFieldWidth(8) << qSetPadChar(QLatin1Char('0'))
640  << lowB << ", high byte=" << highB;
641  }
642  return dt;
643 }
644 
645 QString formatRecipient(const QMap<int, KTnef::KTNEFProperty *> &props)
646 {
647  QString s, dn, addr, t;
649  if ((it = props.find(0x3001)) != props.end()) {
650  dn = (*it)->valueString();
651  }
652  if ((it = props.find(0x3003)) != props.end()) {
653  addr = (*it)->valueString();
654  }
655  if ((it = props.find(0x0C15)) != props.end()) {
656  switch ((*it)->value().toInt()) {
657  case 0:
658  t = QStringLiteral("From:");
659  break;
660  case 1:
661  t = QStringLiteral("To:");
662  break;
663  case 2:
664  t = QStringLiteral("Cc:");
665  break;
666  case 3:
667  t = QStringLiteral("Bcc:");
668  break;
669  }
670  }
671  if (!t.isEmpty()) {
672  s.append(t);
673  }
674  if (!dn.isEmpty()) {
675  s.append(QLatin1Char(' ') + dn);
676  }
677  if (!addr.isEmpty() && addr != dn) {
678  s.append(QLatin1String(" <") + addr + QLatin1Char('>'));
679  }
680 
681  return s.trimmed();
682 }
683 
684 QDateTime readTNEFDate(QDataStream &stream)
685 {
686  // 14-bytes long
687  quint16 y, m, d, hh, mm, ss, dm;
688  stream >> y >> m >> d >> hh >> mm >> ss >> dm;
689  return QDateTime(QDate(y, m, d), QTime(hh, mm, ss));
690 }
691 
692 QString readTNEFAddress(QDataStream &stream)
693 {
694  quint16 totalLen, strLen, addrLen;
695  QString s;
696  stream >> totalLen >> totalLen >> strLen >> addrLen;
697  s.append(readMAPIString(stream, false, false, strLen));
698  s.append(QLatin1String(" <"));
699  s.append(readMAPIString(stream, false, false, addrLen));
700  s.append(QLatin1String(">"));
701  quint8 c;
702  for (int i = 8 + strLen + addrLen; i < totalLen; i++) {
703  stream >> c;
704  }
705  return s;
706 }
707 
708 QByteArray readTNEFData(QDataStream &stream, quint32 len)
709 {
710  QByteArray array(len, '\0');
711  if (len > 0) {
712  stream.readRawData(array.data(), len);
713  }
714  return array;
715 }
716 
717 QVariant readTNEFAttribute(QDataStream &stream, quint16 type, quint32 len)
718 {
719  switch (type) {
720  case atpTEXT:
721  case atpSTRING:
722  return readMAPIString(stream, false, false, len);
723  case atpDATE:
724  return readTNEFDate(stream);
725  default:
726  return readTNEFData(stream, len);
727  }
728 }
729 
730 QString readMAPIString(QDataStream &stream, bool isUnicode, bool align,
731  int len_)
732 {
733  quint32 len;
734  char *buf = nullptr;
735  if (len_ == -1) {
736  stream >> len;
737  } else {
738  len = len_;
739  }
740  if (len > INT_MAX)
741  return QString();
742 
743  quint32 fullLen = len;
744  if (align) {
745  ALIGN(fullLen, 4);
746  }
747  buf = new char[ len ];
748  stream.readRawData(buf, len);
749  quint8 c;
750  for (uint i = len; i < fullLen; i++) {
751  stream >> c;
752  }
753  QString res;
754  if (isUnicode) {
755  res = QString::fromUtf16((const unsigned short *)buf);
756  } else {
757  res = QString::fromLatin1(buf);
758  }
759  delete [] buf;
760  return res;
761 }
762 
763 quint16 readMAPIValue(QDataStream &stream, MAPI_value &mapi)
764 {
765  quint32 d;
766 
767  clearMAPIValue(mapi);
768  stream >> d;
769  mapi.type = (d & 0x0000FFFF);
770  mapi.tag = ((d & 0xFFFF0000) >> 16);
771  if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
772  // skip GUID
773  stream >> d >> d >> d >> d;
774  // name type
775  stream >> mapi.name.type;
776  // name
777  if (mapi.name.type == 0) {
778  uint tmp;
779  stream >> tmp;
780  mapi.name.value.setValue(tmp);
781  } else if (mapi.name.type == 1) {
782  mapi.name.value.setValue(readMAPIString(stream, true));
783  }
784  }
785 
786  int n = 1;
787  QVariant value;
788  if (ISVECTOR(mapi)) {
789  stream >> n;
790  mapi.value = QList<QVariant>();
791  }
792  for (int i = 0; i < n; i++) {
793  value.clear();
794  switch (mapi.type & 0x0FFF) {
795  case MAPI_TYPE_UINT16:
796  stream >> d;
797  value.setValue(d & 0x0000FFFF);
798  break;
799  case MAPI_TYPE_BOOLEAN:
800  case MAPI_TYPE_ULONG: {
801  uint tmp;
802  stream >> tmp;
803  value.setValue(tmp);
804  }
805  break;
806  case MAPI_TYPE_FLOAT:
807  // FIXME: Don't we have to set the value here
808  stream >> d;
809  break;
810  case MAPI_TYPE_DOUBLE: {
811  double tmp;
812  stream >> tmp;
813  value.setValue(tmp);
814  }
815  break;
816  case MAPI_TYPE_TIME: {
817  quint32 lowB, highB;
818  stream >> lowB >> highB;
819  value = formatTime(lowB, highB);
820  }
821  break;
822  case MAPI_TYPE_USTRING:
823  case MAPI_TYPE_STRING8:
824  // in case of a vector'ed value, the number of elements
825  // has already been read in the upper for-loop
826  if (ISVECTOR(mapi)) {
827  d = 1;
828  } else {
829  stream >> d;
830  }
831  for (uint i = 0; i < d; i++) {
832  value.clear();
833  value.setValue(readMAPIString(stream, (mapi.type & 0x0FFF) == MAPI_TYPE_USTRING));
834  }
835  break;
836  case MAPI_TYPE_OBJECT:
837  case MAPI_TYPE_BINARY:
838  if (ISVECTOR(mapi)) {
839  d = 1;
840  } else {
841  stream >> d;
842  }
843  for (uint i = 0; i < d && !stream.atEnd(); i++) {
844  value.clear();
845  quint32 len;
846  stream >> len;
847  value = QByteArray(len, '\0');
848  if (len > 0 && len <= INT_MAX) {
849  uint fullLen = len;
850  ALIGN(fullLen, 4);
851  stream.readRawData(value.toByteArray().data(), len);
852  quint8 c;
853  for (uint i = len; i < fullLen; i++) {
854  stream >> c;
855  }
856  // FIXME: Shouldn't we do something with the value???
857  }
858  }
859  break;
860  default:
861  mapi.type = MAPI_TYPE_NONE;
862  break;
863  }
864  if (ISVECTOR(mapi)) {
865  QList <QVariant> lst = mapi.value.toList();
866  lst << value;
867  mapi.value.setValue(lst);
868  } else {
869  mapi.value = value;
870  }
871  }
872  return mapi.tag;
873 }
874 //@endcond
875 
876 bool KTNEFParser::ParserPrivate::readMAPIProperties(QMap<int, KTNEFProperty *> &props,
877  KTNEFAttach *attach)
878 {
879  quint32 n;
880  MAPI_value mapi;
881  KTNEFProperty *p;
883  bool foundAttachment = false;
884 
885  // some initializations
886  mapi.type = MAPI_TYPE_NONE;
887  mapi.value.clear();
888 
889  // get number of properties
890  stream_ >> n;
891  qCDebug(KTNEF_LOG) << "MAPI Properties:" << n;
892  for (uint i = 0; i < n; i++) {
893  if (stream_.atEnd()) {
894  clearMAPIValue(mapi);
895  return false;
896  }
897  readMAPIValue(stream_, mapi);
898  if (mapi.type == MAPI_TYPE_NONE) {
899  qCDebug(KTNEF_LOG).nospace() << "MAPI unsupported: tag="
900  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
901  << hex
902  #else
903  << Qt::hex
904  #endif
905  << mapi.tag << ", type=" << mapi.type;
906  clearMAPIValue(mapi);
907  return false;
908  }
909  int key = mapi.tag;
910  switch (mapi.tag) {
911  case MAPI_TAG_DATA: {
912  if (mapi.type == MAPI_TYPE_OBJECT && attach) {
913  QByteArray data = mapi.value.toByteArray();
914  int len = data.size();
915  ALIGN(len, 4);
916  device_->seek(device_->pos() - len);
917  quint32 interface_ID;
918  stream_ >> interface_ID;
919  if (interface_ID == MAPI_IID_IMessage) {
920  // embedded TNEF file
921  attach->unsetDataParser();
922  attach->setOffset(device_->pos() + 12);
923  attach->setSize(data.size() - 16);
924  attach->setMimeTag(QStringLiteral("application/vnd.ms-tnef"));
925  attach->setDisplayName(QStringLiteral("Embedded Message"));
926  qCDebug(KTNEF_LOG) << "MAPI Embedded Message: size=" << data.size();
927  }
928  device_->seek(device_->pos() + (len - 4));
929  break;
930  } else if (mapi.type == MAPI_TYPE_BINARY && attach && attach->offset() < 0) {
931  foundAttachment = true;
932  int len = mapi.value.toByteArray().size();
933  ALIGN(len, 4);
934  attach->setSize(len);
935  attach->setOffset(device_->pos() - len);
936  attach->addAttribute(attATTACHDATA, atpBYTE, QStringLiteral("< size=%1 >").arg(len), false);
937  }
938  }
939  qCDebug(KTNEF_LOG) << "MAPI data: size=" << mapi.value.toByteArray().size();
940  break;
941  default: {
942  QString mapiname = QLatin1String("");
943  if (mapi.tag >= 0x8000 && mapi.tag <= 0xFFFE) {
944  if (mapi.name.type == 0) {
945  mapiname = QString::asprintf(" [name = 0x%04x]", mapi.name.value.toUInt());
946  } else {
947  mapiname = QStringLiteral(" [name = %1]").arg(mapi.name.value.toString());
948  }
949  }
950  switch (mapi.type & 0x0FFF) {
951  case MAPI_TYPE_UINT16:
952  qCDebug(KTNEF_LOG).nospace() << "(tag="
953  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
954  << hex
955  #else
956  << Qt::hex
957  #endif
958  << mapi.tag
959  << ") MAPI short" << mapiname.toLatin1().data()
960  << ":"
961  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
962  << hex
963  #else
964  << Qt::hex
965  #endif
966  << mapi.value.toUInt();
967  break;
968  case MAPI_TYPE_ULONG:
969  qCDebug(KTNEF_LOG).nospace() << "(tag="
970  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
971  << hex
972  #else
973  << Qt::hex
974  #endif
975  << mapi.tag
976  << ") MAPI long" << mapiname.toLatin1().data()
977  << ":"
978  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
979  << hex
980  #else
981  << Qt::hex
982  #endif
983  << mapi.value.toUInt();
984  break;
985  case MAPI_TYPE_BOOLEAN:
986  qCDebug(KTNEF_LOG).nospace() << "(tag="
987  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
988  << hex
989  #else
990  << Qt::hex
991  #endif
992  << mapi.tag
993  << ") MAPI boolean" << mapiname.toLatin1().data()
994  << ":" << mapi.value.toBool();
995  break;
996  case MAPI_TYPE_TIME:
997  qCDebug(KTNEF_LOG).nospace() << "(tag="
998  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
999  << hex
1000  #else
1001  << Qt::hex
1002  #endif
1003  << mapi.tag
1004  << ") MAPI time" << mapiname.toLatin1().data()
1005  << ":" << mapi.value.toString().toLatin1().data();
1006  break;
1007  case MAPI_TYPE_USTRING:
1008  case MAPI_TYPE_STRING8:
1009  qCDebug(KTNEF_LOG).nospace() << "(tag="
1010  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
1011  << hex
1012  #else
1013  << Qt::hex
1014  #endif
1015  << mapi.tag
1016  << ") MAPI string" << mapiname.toLatin1().data()
1017  << ":size=" << mapi.value.toByteArray().size()
1018  << mapi.value.toString();
1019  break;
1020  case MAPI_TYPE_BINARY:
1021  qCDebug(KTNEF_LOG).nospace() << "(tag="
1022  #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
1023  << hex
1024  #else
1025  << Qt::hex
1026  #endif
1027  << mapi.tag
1028  << ") MAPI binary" << mapiname.toLatin1().data()
1029  << ":size=" << mapi.value.toByteArray().size();
1030  break;
1031  }
1032  }
1033  break;
1034  }
1035  // do not remove potential existing similar entry
1036  if ((it = props.constFind(key)) == props.constEnd()) {
1037  p = new KTNEFProperty(key, (mapi.type & 0x0FFF),
1038  mapi.value, mapi.name.value);
1039  props[ p->key() ] = p;
1040  }
1041  //qCDebug(KTNEF_LOG) << "stream:" << device_->pos();
1042  }
1043 
1044  if (foundAttachment && attach) {
1045  attach->setIndex(attach->property(MAPI_TAG_INDEX).toUInt());
1046  attach->setDisplaySize(attach->property(MAPI_TAG_SIZE).toUInt());
1047  QString str = attach->property(MAPI_TAG_DISPLAYNAME).toString();
1048  if (!str.isEmpty()) {
1049  attach->setDisplayName(str);
1050  }
1051  attach->setFileName(attach->property(MAPI_TAG_FILENAME).toString());
1052  str = attach->property(MAPI_TAG_MIMETAG).toString();
1053  if (!str.isEmpty()) {
1054  attach->setMimeTag(str);
1055  }
1056  attach->setExtension(attach->property(MAPI_TAG_EXTENSION).toString());
1057  if (attach->name().isEmpty()) {
1058  attach->setName(attach->fileName());
1059  }
1060  }
1061 
1062  return true;
1063 }
QByteArray toByteArray() const const
void setOffset(int offset)
Sets the offset value of this attachment to offset.
QVariant property(int key) const
Returns the property associated with the specified key.
QString & append(QChar ch)
QString name(const QVariant &location)
QString writableLocation(QStandardPaths::StandardLocation type)
QString asprintf(const char *cformat,...)
void setDisplaySize(int size)
Sets the display size of the attachment to size.
bool extractAll()
Extracts all TNEF attachments into the default directory.
void reserve(int alloc)
int key() const
Returns the integer key of the property.
~KTNEFParser()
Destroys the TNEF parser object.
void setDefaultExtractDir(const QString &dirname)
Sets the default extraction directory to dirname.
T value() const const
QMap::const_iterator constFind(const Key &key) const const
virtual bool open(QIODevice::OpenMode mode) override
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
void setExtension(const QString &str)
Sets the filename extension of this attachment to str.
This file is part of the API for handling TNEF data and defines the KTNEFMessage class.
bool exists() const const
bool extractFileTo(const QString &filename, const QString &dirname) const
Extracts a TNEF attachment having filename filename into the directory dirname.
int readRawData(char *s, int len)
bool extractFile(const QString &filename) const
Extracts a TNEF attachment having filename filename into the default directory.
T value(int i) const const
QMimeType mimeTypeForFile(const QString &fileName, QMimeDatabase::MatchMode mode) const const
Represents a TNEF message.
Definition: ktnefmessage.h:38
void addAttribute(int key, int type, const QVariant &value, bool overwrite=false)
Adds a TNEF attribute.
This file is part of the API for handling TNEF data and provides some basic definitions for general u...
uint toUInt(bool *ok) const const
bool commit()
QString fromUtf16(const ushort *unicode, int size)
QString fileName() const const
Type type(const QSqlDatabase &db)
This file is part of the API for handling TNEF data and defines the KTNEFParser class.
int size() const
Returns the size of the attachment.
QString absoluteFilePath() const const
bool isEmpty() const const
QMimeType mimeTypeForData(const QByteArray &data) const const
QString trimmed() const const
QMap::const_iterator constEnd() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString fileName() const
Returns the filename of the attachment.
QMap::iterator end()
void setMimeTag(const QString &str)
Sets the MIME tag of this attachment to str.
bool atEnd() const const
void setFileName(const QString &str)
Sets the filename of this attachment to str.
void unsetDataParser()
Unsets the DataParsed flag for this attachment.
Definition: ktnefattach.cpp:68
Represents a TNEF attachment.
Definition: ktnefattach.h:38
QTextStream & showbase(QTextStream &stream)
void clear()
void setValue(const T &value)
bool openDevice(QIODevice *device)
Opens the QIODevice device for parsing.
bool isValid() const const
bool openFile(const QString &filename) const
Opens the filename for parsing.
void setName(const QString &str)
Sets the name of this attachment to str.
QByteArray toLatin1() const const
This file is part of the API for handling TNEF data and defines the KTNEFProperty class...
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
int size() const const
void setSize(int size)
Sets the size of the attachment to size.
char * data()
qint64 write(const char *data, qint64 maxSize)
QString fromLatin1(const char *str, int size)
QTextStream & hex(QTextStream &stream)
int offset() const
Returns the offset value of the attachment.
Definition: ktnefattach.cpp:98
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
KTNEFParser()
Constructs a TNEF parser object.
int size() const const
This file is part of the API for handling TNEF data and defines the KTNEFAttach class.
void setIndex(int indx)
Sets the index of this attachment to indx.
KTNEFMessage * message() const
Returns the KTNEFMessage used in the parsing process.
QString toString() const const
QString name() const
Returns the name of the attachment.
Interface for setting MAPI properties.
Definition: ktnefproperty.h:33
QMap::iterator find(const Key &key)
void setDisplayName(const QString &str)
Sets the display name of this attachment to str.
QTextStream & dec(QTextStream &stream)
const T value(const Key &key, const T &defaultValue) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Wed Oct 28 2020 23:21:57 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.