KTnef

formatter.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
3 SPDX-FileCopyrightText: 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
4 SPDX-FileCopyrightText: 2005 Rafal Rzepecki <divide@users.sourceforge.net>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8/**
9 @file
10 This file is part of the API for handling TNEF data and provides
11 static Formatter helpers.
12
13 @brief
14 Provides helpers too format @acronym TNEF attachments into different
15 formats like eg. a HTML representation.
16
17 @author Cornelius Schumacher
18 @author Reinhold Kainhofer
19 @author Rafal Rzepecki
20*/
21
22#include "formatter.h"
23using namespace Qt::Literals::StringLiterals;
24
25#include "ktnefdefs.h"
26#include "ktnefmessage.h"
27#include "ktnefparser.h"
28
29#include <KContacts/PhoneNumber>
30#include <KContacts/VCardConverter>
31
32#include <KCalUtils/IncidenceFormatter>
33#include <KCalendarCore/Calendar>
34#include <KCalendarCore/ICalFormat>
35
36#include <KLocalizedString>
37
38#include <QBuffer>
39#include <QTimeZone>
40
41#include <ctime>
42
43using namespace KCalendarCore;
44using namespace KTnef;
45
46/*******************************************************************
47 * Helper functions for the msTNEF -> VPart converter
48 *******************************************************************/
49
50//-----------------------------------------------------------------------------
51//@cond IGNORE
52static QString stringProp(KTNEFMessage *tnefMsg, quint32 key, const QString &fallback = QString())
53{
54 return tnefMsg->findProp(key < 0x10000 ? key & 0xFFFF : key >> 16, fallback);
55}
56
57static QString sNamedProp(KTNEFMessage *tnefMsg, const QString &name, const QString &fallback = QString())
58{
59 return tnefMsg->findNamedProp(name, fallback);
60}
61
62static QDateTime pureISOToLocalQDateTime(const QString &dtStr)
63{
64 const QStringView dtView{dtStr};
65 const int year = dtView.left(4).toInt();
66 const int month = dtView.mid(4, 2).toInt();
67 const int day = dtView.mid(6, 2).toInt();
68 const int hour = dtView.mid(9, 2).toInt();
69 const int minute = dtView.mid(11, 2).toInt();
70 const int second = dtView.mid(13, 2).toInt();
71 QDate tmpDate;
72 tmpDate.setDate(year, month, day);
73 QTime tmpTime;
74 tmpTime.setHMS(hour, minute, second);
75
76 if (tmpDate.isValid() && tmpTime.isValid()) {
77 QDateTime dT = QDateTime(tmpDate, tmpTime);
78
79 // correct for GMT ( == Zulu time == UTC )
80 if (dtStr.at(dtStr.length() - 1) == QLatin1Char('Z')) {
81 // dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
82 // localUTCOffset( dT ) );
83 dT = dT.toLocalTime();
84 }
85 return dT;
86 } else {
87 return {};
88 }
89}
90//@endcond
91
93{
94 KTNEFParser parser;
95 QByteArray b(tnef);
96 QBuffer buf(&b);
98 KContacts::Addressee addressee;
99 ICalFormat calFormat;
100 Event::Ptr event(new Event());
101
102 if (parser.openDevice(&buf)) {
103 KTNEFMessage *tnefMsg = parser.message();
104 // QMap<int,KTNEFProperty*> props = parser.message()->properties();
105
106 // Everything depends from property PR_MESSAGE_CLASS
107 // (this is added by KTNEFParser):
108 QString msgClass = tnefMsg->findProp(0x001A, QString(), true).toUpper();
109 if (!msgClass.isEmpty()) {
110 // Match the old class names that might be used by Outlook for
111 // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
112 bool bCompatClassAppointment = false;
113 bool bCompatMethodRequest = false;
114 bool bCompatMethodCancled = false;
115 bool bCompatMethodAccepted = false;
116 bool bCompatMethodAcceptedCond = false;
117 bool bCompatMethodDeclined = false;
118 if (msgClass.startsWith("IPM.MICROSOFT SCHEDULE."_L1)) {
119 bCompatClassAppointment = true;
120 if (msgClass.endsWith(".MTGREQ"_L1)) {
121 bCompatMethodRequest = true;
122 } else if (msgClass.endsWith(".MTGCNCL"_L1)) {
123 bCompatMethodCancled = true;
124 } else if (msgClass.endsWith(".MTGRESPP"_L1)) {
125 bCompatMethodAccepted = true;
126 } else if (msgClass.endsWith(".MTGRESPA"_L1)) {
127 bCompatMethodAcceptedCond = true;
128 } else if (msgClass.endsWith(".MTGRESPN"_L1)) {
129 bCompatMethodDeclined = true;
130 }
131 }
132 bool bCompatClassNote = (msgClass == "IPM.MICROSOFT MAIL.NOTE"_L1);
133
134 if (bCompatClassAppointment || "IPM.APPOINTMENT"_L1 == msgClass) {
135 // Compose a vCal
136 bool bIsReply = false;
137 QString prodID = QStringLiteral("-//Microsoft Corporation//Outlook ");
138 prodID += tnefMsg->findNamedProp(QStringLiteral("0x8554"), QStringLiteral("9.0"));
139 prodID += "MIMEDIR/EN\n"_L1;
140 prodID += "VERSION:2.0\n"_L1;
141 calFormat.setApplication(QStringLiteral("Outlook"), prodID);
142
143 // iTIPMethod method;
144 if (bCompatMethodRequest) {
145 // method = iTIPRequest;
146 } else if (bCompatMethodCancled) {
147 // method = iTIPCancel;
148 } else if (bCompatMethodAccepted || bCompatMethodAcceptedCond || bCompatMethodDeclined) {
149 // method = iTIPReply;
150 bIsReply = true;
151 } else {
152 // pending(khz): verify whether "0x0c17" is the right tag ???
153 //
154 // at the moment we think there are REQUESTS and UPDATES
155 //
156 // but WHAT ABOUT REPLIES ???
157 //
158 //
159
160 if (tnefMsg->findProp(0x0c17) == QLatin1Char('1')) {
161 bIsReply = true;
162 }
163 // method = iTIPRequest;
164 }
165
166 /// ### FIXME Need to get this attribute written
167 // ScheduleMessage schedMsg( event, method, ScheduleMessage::Unknown );
168
169 QString sSenderSearchKeyEmail(tnefMsg->findProp(0x0C1D));
170 if (sSenderSearchKeyEmail.isEmpty()) {
171 sSenderSearchKeyEmail = tnefMsg->findProp(0x0C1f);
172 }
173
174 if (!sSenderSearchKeyEmail.isEmpty()) {
175 const int colon = sSenderSearchKeyEmail.indexOf(QLatin1Char(':'));
176 // May be e.g. "SMTP:KHZ@KDE.ORG"
177 if (colon == -1) {
178 sSenderSearchKeyEmail.remove(0, colon + 1);
179 }
180 }
181
182 QString s(tnefMsg->findProp(0x8189));
183 const QStringList attendees = s.split(QLatin1Char(';'));
184 if (!attendees.isEmpty()) {
185 for (auto it = attendees.cbegin(), end = attendees.cend(); it != end; ++it) {
186 // Skip all entries that have no '@' since these are
187 // no mail addresses
188 if (!(*it).contains(QLatin1Char('@'))) {
189 s = (*it).trimmed();
190
191 Attendee attendee(s, s, true);
192 if (bIsReply) {
193 if (bCompatMethodAccepted) {
195 }
196 if (bCompatMethodDeclined) {
198 }
199 if (bCompatMethodAcceptedCond) {
201 }
202 } else {
205 }
206 event->addAttendee(attendee);
207 }
208 }
209 } else {
210 // Oops, no attendees?
211 // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
212 s = sSenderSearchKeyEmail;
213 if (!s.isEmpty()) {
214 Attendee attendee(QString(), QString(), true);
215 if (bIsReply) {
216 if (bCompatMethodAccepted) {
218 }
219 if (bCompatMethodAcceptedCond) {
221 }
222 if (bCompatMethodDeclined) {
224 }
225 } else {
228 }
229 event->addAttendee(attendee);
230 }
231 }
232 s = tnefMsg->findProp(0x3ff8); // look for organizer property
233 if (s.isEmpty() && !bIsReply) {
234 s = sSenderSearchKeyEmail;
235 }
236 // TODO: Use the common name?
237 if (!s.isEmpty()) {
238 event->setOrganizer(s);
239 }
240
241 QDateTime dt = tnefMsg->property(0x819b).toDateTime();
242 if (!dt.isValid()) {
243 dt = tnefMsg->property(0x0060).toDateTime();
244 }
245 event->setDtStart(dt); // ## Format??
246
247 dt = tnefMsg->property(0x819c).toDateTime();
248 if (!dt.isValid()) {
249 dt = tnefMsg->property(0x0061).toDateTime();
250 }
251 event->setDtEnd(dt);
252
253 s = tnefMsg->findProp(0x810d);
254 event->setLocation(s);
255 // is it OK to set this to OPAQUE always ??
256 // vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
257 // vPart += "SEQUENCE:0\n";
258
259 // is "0x0023" OK - or should we look for "0x0003" ??
260 s = tnefMsg->findProp(0x0062);
261 event->setUid(s);
262
263 // PENDING(khz): is this value in local timezone? Must it be
264 // adjusted? Most likely this is a bug in the server or in
265 // Outlook - we ignore it for now.
266 s = tnefMsg->findProp(0x8202).remove(QLatin1Char('-')).remove(QLatin1Char(':'));
267 // ### kcal always uses currentDateTime()
268 // event->setDtStamp( QDateTime::fromString( s ) );
269
270 s = tnefMsg->findNamedProp(QStringLiteral("Keywords"));
271 event->setCategories(s);
272
273 s = tnefMsg->findProp(0x1000);
274 if (s.isEmpty()) {
275 s = tnefMsg->findProp(0x3fd9);
276 }
277 event->setDescription(s);
278
279 s = tnefMsg->findProp(0x0070);
280 if (s.isEmpty()) {
281 s = tnefMsg->findProp(0x0037);
282 }
283 event->setSummary(s);
284
285 s = tnefMsg->findProp(0x0026);
286 event->setPriority(s.toInt());
287 // is reminder flag set ?
288 if (!tnefMsg->findProp(0x8503).isEmpty()) {
289 Alarm::Ptr alarm(new Alarm(event.data())); // TODO: fix when KCalendarCore::Alarm is fixed
290 QDateTime highNoonTime = pureISOToLocalQDateTime(tnefMsg->findProp(0x8502).remove(QLatin1Char('-')).remove(QLatin1Char(':')));
291 QDateTime wakeMeUpTime = pureISOToLocalQDateTime(tnefMsg->findProp(0x8560, QString()).remove(QLatin1Char('-')).remove(QLatin1Char(':')));
292 alarm->setTime(wakeMeUpTime);
293
294 if (highNoonTime.isValid() && wakeMeUpTime.isValid()) {
295 alarm->setStartOffset(Duration(highNoonTime, wakeMeUpTime));
296 } else {
297 // default: wake them up 15 minutes before the appointment
298 alarm->setStartOffset(Duration(15 * 60));
299 }
300 alarm->setDisplayAlarm(i18n("Reminder"));
301
302 // Sorry: the different action types are not known (yet)
303 // so we always set 'DISPLAY' (no sounds, no images...)
304 event->addAlarm(alarm);
305 }
306 // ensure we have a uid for this event
307 if (event->uid().isEmpty()) {
308 event->setUid(CalFormat::createUniqueId());
309 }
310 cal->addEvent(event);
311 // bOk = true;
312 // we finished composing a vCal
313 } else if (bCompatClassNote || "IPM.CONTACT"_L1 == msgClass) {
314 addressee.setUid(stringProp(tnefMsg, attMSGID));
315 addressee.setFormattedName(stringProp(tnefMsg, MAPI_TAG_PR_DISPLAY_NAME));
316 KContacts::Email email(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS)));
317 email.setPreferred(true);
318 addressee.addEmail(email);
319 addressee.addEmail(KContacts::Email(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS))));
320 addressee.addEmail(KContacts::Email(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS))));
321 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"),
322 QStringLiteral("X-IMAddress"),
323 sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_IMADDRESS)));
324 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-SpousesName"), stringProp(tnefMsg, MAPI_TAG_PR_SPOUSE_NAME));
325 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-ManagersName"), stringProp(tnefMsg, MAPI_TAG_PR_MANAGER_NAME));
326 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-AssistantsName"), stringProp(tnefMsg, MAPI_TAG_PR_ASSISTANT));
327 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Department"), stringProp(tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME));
328 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Office"), stringProp(tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION));
329 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Profession"), stringProp(tnefMsg, MAPI_TAG_PR_PROFESSION));
330
331 QString s = tnefMsg->findProp(MAPI_TAG_PR_WEDDING_ANNIVERSARY).remove(QLatin1Char('-')).remove(QLatin1Char(':'));
332 if (!s.isEmpty()) {
333 addressee.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Anniversary"), s);
334 }
335
337 url.setUrl(QUrl(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_WEBPAGE))));
338
339 addressee.setUrl(url);
340
341 // collect parts of Name entry
342 addressee.setFamilyName(stringProp(tnefMsg, MAPI_TAG_PR_SURNAME));
343 addressee.setGivenName(stringProp(tnefMsg, MAPI_TAG_PR_GIVEN_NAME));
344 addressee.setAdditionalName(stringProp(tnefMsg, MAPI_TAG_PR_MIDDLE_NAME));
345 addressee.setPrefix(stringProp(tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX));
346 addressee.setSuffix(stringProp(tnefMsg, MAPI_TAG_PR_GENERATION));
347
348 addressee.setNickName(stringProp(tnefMsg, MAPI_TAG_PR_NICKNAME));
349 addressee.setRole(stringProp(tnefMsg, MAPI_TAG_PR_TITLE));
350 addressee.setOrganization(stringProp(tnefMsg, MAPI_TAG_PR_COMPANY_NAME));
351 /*
352 the MAPI property ID of this (multiline) )field is unknown:
353 vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
354 */
355
357 adr.setPostOfficeBox(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX));
358 adr.setStreet(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET));
359 adr.setLocality(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY));
360 adr.setRegion(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE));
361 adr.setPostalCode(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE));
362 adr.setCountry(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY));
364 addressee.insertAddress(adr);
365
366 adr.setPostOfficeBox(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX)));
367 adr.setStreet(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET)));
368 adr.setLocality(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_BUSINESSADDRESSCITY)));
369 adr.setRegion(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE)));
370 adr.setPostalCode(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE)));
371 adr.setCountry(sNamedProp(tnefMsg, QStringLiteral(MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY)));
373 addressee.insertAddress(adr);
374
375 adr.setPostOfficeBox(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX));
376 adr.setStreet(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET));
377 adr.setLocality(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY));
378 adr.setRegion(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE));
379 adr.setPostalCode(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE));
380 adr.setCountry(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY));
382 addressee.insertAddress(adr);
383
384 // problem: the 'other' address was stored by KOrganizer in
385 // a line looking like the following one:
386 // vPart += "\nADR;TYPE=dom;TYPE=intl;TYPE=parcel;TYPE=postal;TYPE=work;"
387 // "TYPE=home:other_pobox;;other_str1\nother_str2;other_loc;other_region;"
388 // "other_pocode;other_country"
389
390 QString nr;
391 nr = stringProp(tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER);
393 nr = stringProp(tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER);
395 nr = stringProp(tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER);
397 nr = stringProp(tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER);
399 nr = stringProp(tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER);
401
402 s = tnefMsg->findProp(MAPI_TAG_PR_BIRTHDAY).remove(QLatin1Char('-')).remove(QLatin1Char(':'));
403 if (!s.isEmpty()) {
405 }
406
407 // bOk = (!addressee.isEmpty());
408 } else if ("IPM.NOTE"_L1 == msgClass) {
409 } // else if ... and so on ...
410 }
411 }
412
413 // Compose return string
414 const QString iCal = calFormat.toString(cal);
415 if (!iCal.isEmpty()) {
416 // This was an iCal
417 return iCal;
418 }
419
420 // Not an iCal - try a vCard
422 return QString::fromUtf8(converter.createVCard(addressee));
423}
424
426{
427 const QString vPart = msTNEFToVPart(tnef);
429 if (!iCal.isEmpty()) {
430 return iCal;
431 } else {
432 return vPart;
433 }
434}
void setStatus(PartStat status)
void setRole(Role role)
static QString createUniqueId()
static void setApplication(const QString &application, const QString &productID)
QString toString(const Calendar::Ptr &calendar) override
void setType(Type type)
void setStreet(const QString &street)
void setCountry(const QString &country)
void setPostOfficeBox(const QString &postOfficeBox)
void setRegion(const QString &region)
void setPostalCode(const QString &code)
void setLocality(const QString &locality)
void addEmail(const Email &email)
void setAdditionalName(const QString &additionalName)
void setOrganization(const QString &organization)
void insertPhoneNumber(const PhoneNumber &phoneNumber)
void setPrefix(const QString &prefix)
void insertCustom(const QString &app, const QString &name, const QString &value)
void setSuffix(const QString &suffix)
void setGivenName(const QString &givenName)
void setNickName(const QString &nickName)
void setBirthday(const QDate &birthday)
void insertAddress(const Address &address)
void setRole(const QString &role)
void setFormattedName(const QString &formattedName)
void setFamilyName(const QString &familyName)
void setUid(const QString &uid)
void setUrl(const ResourceLocatorUrl &url)
void setPreferred(bool preferred)
QByteArray createVCard(const Addressee &addr, Version version=v3_0) const
Represents a TNEF message.
Provides an TNEF parser.
Definition ktnefparser.h:37
bool openDevice(QIODevice *device)
Opens the QIODevice device for parsing.
KTNEFMessage * message() const
Returns the KTNEFMessage used in the parsing process.
QString findProp(int key, const QString &fallback=QString(), bool convertToUpper=false) const
Finds a property by key, returning a formatted value.
QString findNamedProp(const QString &name, const QString &fallback=QString(), bool convertToUpper=false) const
Finds a property by name, returning a formatted value.
QVariant property(int key) const
Returns the property associated with the specified key.
This file is part of the API for handling TNEF data and provides static Formatter helpers.
KTNEF_EXPORT QString formatTNEFInvitation(const QByteArray &tnef, const KCalendarCore::MemoryCalendar::Ptr &cal, KCalUtils::InvitationFormatterHelper *h)
Formats a TNEF attachment to an HTML mail.
KTNEF_EXPORT QString msTNEFToVPart(const QByteArray &tnef)
Transforms a TNEF attachment to an iCal or vCard.
Definition formatter.cpp:92
QString i18n(const char *text, const TYPE &arg...)
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.
KCALUTILS_EXPORT QString formatICalInvitation(const QString &invitation, const KCalendarCore::Calendar::Ptr &calendar, InvitationFormatterHelper *helper)
bool isValid(int year, int month, int day)
bool setDate(int year, int month, int day)
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isValid() const const
QDateTime toLocalTime() const const
const_iterator cbegin() const const
const_iterator cend() const const
bool isEmpty() const const
T * data() const const
const QChar at(qsizetype position) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString toUpper() const const
QString trimmed() const const
QStringView left(qsizetype length) const const
int toInt(bool *ok, int base) const const
bool isValid(int h, int m, int s, int ms)
bool setHMS(int h, int m, int s, int ms)
QTimeZone utc()
QDateTime toDateTime() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 8 2024 11:57:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.