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

KDE's Doxygen guidelines are available online.