vevent.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include "vevent.h"
  19. #include <mapiutil.h>
  20. #include <kopano/mapiext.h>
  21. #include <kopano/CommonUtil.h>
  22. #include "nameids.h"
  23. #include "icaluid.h"
  24. #include <kopano/stringutil.h>
  25. #include "icalmem.hpp"
  26. namespace KC {
  27. /**
  28. * VEvent constructor, implements VConverter
  29. */
  30. VEventConverter::VEventConverter(LPADRBOOK lpAdrBook, timezone_map *mapTimeZones, LPSPropTagArray lpNamedProps, const std::string& strCharset, bool blCensor, bool bNoRecipients, IMailUser *lpMailUser)
  31. : VConverter(lpAdrBook, mapTimeZones, lpNamedProps, strCharset, blCensor, bNoRecipients, lpMailUser)
  32. {
  33. }
  34. /**
  35. * Entrypoint to convert an ical object to MAPI object.
  36. *
  37. * @param[in] lpEventRoot The root component (VCALENDAR top object)
  38. * @param[in] lpEvent The VEVENT object to convert
  39. * @param[in] lpPrevItem Optional previous (top) item to use when lpEvent describes an exception
  40. * @param[out] lppRet The icalitem struct to finalize into a MAPI object
  41. *
  42. * @return MAPI error code
  43. */
  44. HRESULT VEventConverter::HrICal2MAPI(icalcomponent *lpEventRoot, icalcomponent *lpEvent, icalitem *lpPrevItem, icalitem **lppRet)
  45. {
  46. HRESULT hr = VConverter::HrICal2MAPI(lpEventRoot, lpEvent,
  47. lpPrevItem, lppRet);
  48. if (hr != hrSuccess)
  49. return hr;
  50. (*lppRet)->eType = VEVENT;
  51. return hrSuccess;
  52. }
  53. /**
  54. * The properties set here are all required base properties for
  55. * different calendar items and meeting requests.
  56. *
  57. * Finds the difference if we're handling this message as the
  58. * organiser or as an attendee. Uses that and the method to set the
  59. * correct response and meeting status. PR_MESSAGE_CLASS is only set
  60. * on the main message, not on exceptions. For PUBLISH methods, it
  61. * will also set the appointment reply time property. Lastly, the icon
  62. * index (outlook icon displayed in list view) is set.
  63. *
  64. * @note We only handle the methods REQUEST, REPLY and CANCEL
  65. * according to dagent/spooler/gateway fashion (that is, meeting
  66. * requests in emails) and PUBLISH for iCal/CalDAV (that is, pure
  67. * calendar items). Meeting requests through the PUBLISH method (as
  68. * also described in the Microsoft documentations) is not supported.
  69. *
  70. * @param[in] icMethod Method of the ical event
  71. * @param[in] lpicEvent The ical VEVENT to convert
  72. * @param[in] base Used for the 'base' pointer for memory allocations
  73. * @param[in] bisException Weather we're handling an exception or not
  74. * @param[in,out] lstMsgProps
  75. *
  76. * @return MAPI error code
  77. */
  78. HRESULT VEventConverter::HrAddBaseProperties(icalproperty_method icMethod, icalcomponent *lpicEvent, void *base, bool bisException, std::list<SPropValue> *lstMsgProps)
  79. {
  80. icalproperty *icProp = NULL;
  81. icalparameter *icParam = NULL;
  82. SPropValue sPropVal;
  83. bool bMeeting = false;
  84. bool bMeetingOrganised = false;
  85. std::wstring strEmail;
  86. time_t tNow = 0;
  87. icProp = icalcomponent_get_first_property(lpicEvent, ICAL_ORGANIZER_PROPERTY);
  88. if (icProp)
  89. {
  90. const char *lpszProp = icalproperty_get_organizer(icProp);
  91. strEmail = m_converter.convert_to<std::wstring>(lpszProp, rawsize(lpszProp), m_strCharset.c_str());
  92. if (wcsncasecmp(strEmail.c_str(), L"mailto:", 7) == 0)
  93. strEmail.erase(0, 7);
  94. if (bIsUserLoggedIn(strEmail))
  95. bMeetingOrganised = true;
  96. }
  97. // @note setting PR_MESSAGE_CLASS must be the last entry in the case.
  98. switch (icMethod) {
  99. case ICAL_METHOD_REQUEST:
  100. bMeeting = true;
  101. sPropVal.ulPropTag = PR_RESPONSE_REQUESTED;
  102. sPropVal.Value.b = true;
  103. lstMsgProps->push_back(sPropVal);
  104. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_RESPONSESTATUS], PT_LONG);
  105. sPropVal.Value.ul = respNotResponded;
  106. lstMsgProps->push_back(sPropVal);
  107. HrCopyString(base, L"IPM.Schedule.Meeting.Request", &sPropVal.Value.lpszW);
  108. break;
  109. case ICAL_METHOD_COUNTER:
  110. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_COUNTERPROPOSAL], PT_BOOLEAN);
  111. sPropVal.Value.b = true;
  112. lstMsgProps->push_back(sPropVal);
  113. // Fall through to REPLY
  114. case ICAL_METHOD_REPLY:
  115. // This value with respAccepted/respDeclined/respTentative is only for imported items through the PUBLISH method, which we do not support.
  116. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_RESPONSESTATUS], PT_LONG);
  117. sPropVal.Value.ul = respNone;
  118. lstMsgProps->push_back(sPropVal);
  119. // skip the meetingstatus property
  120. bMeeting = false;
  121. // A reply message must have only one attendee, the attendee replying
  122. if (icalcomponent_count_properties(lpicEvent, ICAL_ATTENDEE_PROPERTY) != 1)
  123. return MAPI_E_CALL_FAILED;
  124. icProp = icalcomponent_get_first_property(lpicEvent, ICAL_ATTENDEE_PROPERTY);
  125. if (icProp == NULL)
  126. return MAPI_E_CALL_FAILED;
  127. icParam = icalproperty_get_first_parameter(icProp, ICAL_PARTSTAT_PARAMETER);
  128. if (icParam == NULL)
  129. return MAPI_E_CALL_FAILED;
  130. switch (icalparameter_get_partstat(icParam)) {
  131. case ICAL_PARTSTAT_ACCEPTED:
  132. HrCopyString(base, L"IPM.Schedule.Meeting.Resp.Pos", &sPropVal.Value.lpszW);
  133. break;
  134. case ICAL_PARTSTAT_DECLINED:
  135. HrCopyString(base, L"IPM.Schedule.Meeting.Resp.Neg", &sPropVal.Value.lpszW);
  136. break;
  137. case ICAL_PARTSTAT_TENTATIVE:
  138. HrCopyString(base, L"IPM.Schedule.Meeting.Resp.Tent", &sPropVal.Value.lpszW);
  139. break;
  140. default:
  141. return MAPI_E_TYPE_NO_SUPPORT;
  142. }
  143. break;
  144. case ICAL_METHOD_CANCEL:
  145. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_RESPONSESTATUS], PT_LONG);
  146. sPropVal.Value.ul = respNotResponded;
  147. lstMsgProps->push_back(sPropVal);
  148. // make sure the cancel flag gets set
  149. bMeeting = true;
  150. HrCopyString(base, L"IPM.Schedule.Meeting.Canceled", &sPropVal.Value.lpszW);
  151. break;
  152. case ICAL_METHOD_PUBLISH:
  153. default:
  154. // set ResponseStatus to 0 fix for BlackBerry.
  155. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_RESPONSESTATUS], PT_LONG);
  156. if (m_ulUserStatus != 0)
  157. sPropVal.Value.ul = m_ulUserStatus;
  158. else if (bMeetingOrganised)
  159. sPropVal.Value.ul = 1;
  160. else
  161. sPropVal.Value.ul = 0;
  162. lstMsgProps->push_back(sPropVal);
  163. // time(NULL) returns UTC time as libical sets application to UTC time.
  164. tNow = time(NULL);
  165. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTREPLYTIME], PT_SYSTIME);
  166. UnixTimeToFileTime(tNow, &sPropVal.Value.ft);
  167. lstMsgProps->push_back(sPropVal);
  168. // Publish is used when mixed events are in the vcalendar
  169. // we should determine on other properties if this is a meeting request related item
  170. HrCopyString(base, L"IPM.Appointment", &sPropVal.Value.lpszW);
  171. // if we don't have attendees, skip the meeting request props
  172. if (icalcomponent_get_first_property(lpicEvent, ICAL_ATTENDEE_PROPERTY) == NULL)
  173. bMeeting = false;
  174. else
  175. bMeeting = true; //if Attendee is present then set the PROP_MEETINGSTATUS property
  176. break;
  177. }
  178. if (!bisException) {
  179. sPropVal.ulPropTag = PR_MESSAGE_CLASS_W;
  180. lstMsgProps->push_back(sPropVal);
  181. }
  182. if (icMethod == ICAL_METHOD_CANCEL || icMethod == ICAL_METHOD_REQUEST)
  183. {
  184. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_REQUESTSENT], PT_BOOLEAN);
  185. sPropVal.Value.b = true;
  186. lstMsgProps->push_back(sPropVal);
  187. } else if (icMethod == ICAL_METHOD_REPLY || icMethod == ICAL_METHOD_COUNTER) {
  188. // This is only because outlook and the examples say so in [MS-OXCICAL].pdf
  189. // Otherwise, it's completely contradictionary to what the documentation describes.
  190. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_REQUESTSENT], PT_BOOLEAN);
  191. sPropVal.Value.b = false;
  192. lstMsgProps->push_back(sPropVal);
  193. } else {
  194. // PUBLISH method, depends on if we're the owner of the object
  195. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_REQUESTSENT], PT_BOOLEAN);
  196. sPropVal.Value.b = bMeetingOrganised;
  197. lstMsgProps->push_back(sPropVal);
  198. }
  199. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_MEETINGSTATUS], PT_LONG);
  200. if (bMeeting) {
  201. // Set meeting status flags
  202. sPropVal.Value.ul = 1 | 2; // this-is-a-meeting-object flag + received flag
  203. if (icMethod == ICAL_METHOD_CANCEL)
  204. sPropVal.Value.ul |= 4; // cancelled flag
  205. else if(bMeetingOrganised)
  206. sPropVal.Value.ul = 1;
  207. } else {
  208. sPropVal.Value.ul = 0; // this-is-a-meeting-object flag off
  209. }
  210. lstMsgProps->push_back(sPropVal);
  211. sPropVal.ulPropTag = PR_ICON_INDEX;
  212. if (!bMeeting)
  213. sPropVal.Value.ul = ICON_APPT_APPOINTMENT;
  214. else if (icMethod == ICAL_METHOD_CANCEL)
  215. sPropVal.Value.ul = ICON_APPT_MEETING_CANCEL;
  216. else
  217. sPropVal.Value.ul = ICON_APPT_MEETING_SINGLE;
  218. // 1024: normal calendar item
  219. // 1025: recurring item
  220. // 1026: meeting request
  221. // 1027: recurring meeting request
  222. lstMsgProps->push_back(sPropVal);
  223. return hrSuccess;
  224. }
  225. /**
  226. * Set time properties in icalitem from the ical data
  227. *
  228. * @param[in] lpicEventRoot ical VCALENDAR component to set the timezone
  229. * @param[in] lpicEvent ical VEVENT component
  230. * @param[in] bIsAllday set times for normal or allday event
  231. * @param[out] lpIcalItem icalitem structure in which mapi properties are set
  232. * @return MAPI error code
  233. * @retval MAPI_E_INVALID_PARAMETER start time or timezone not present in ical data
  234. */
  235. HRESULT VEventConverter::HrAddTimes(icalproperty_method icMethod, icalcomponent *lpicEventRoot, icalcomponent *lpicEvent, bool bIsAllday, icalitem *lpIcalItem)
  236. {
  237. HRESULT hr = hrSuccess;
  238. SPropValue sPropVal;
  239. icalproperty* lpicDTStartProp = NULL;
  240. icalproperty* lpicDTEndProp = NULL;
  241. icalproperty* lpicOrigDTStartProp = NULL;
  242. icalproperty* lpicOrigDTEndProp = NULL;
  243. std::unique_ptr<icalproperty, icalmapi_delete> lpicFreeDTStartProp, lpicFreeDTEndProp;
  244. time_t timeDTStartUTC = 0, timeDTEndUTC = 0;
  245. time_t timeDTStartLocal = 0, timeDTEndLocal = 0;
  246. time_t timeEndOffset = 0, timeStartOffset = 0;
  247. icalproperty* lpicDurationProp = NULL;
  248. icalproperty* lpicProp = NULL;
  249. lpicDTStartProp = icalcomponent_get_first_property(lpicEvent, ICAL_DTSTART_PROPERTY);
  250. lpicDTEndProp = icalcomponent_get_first_property(lpicEvent, ICAL_DTEND_PROPERTY);
  251. // DTSTART must be available
  252. if (lpicDTStartProp == nullptr)
  253. return MAPI_E_INVALID_PARAMETER;
  254. hr = HrAddTimeZone(lpicDTStartProp, lpIcalItem);
  255. if (hr != hrSuccess)
  256. return hr;
  257. if (icMethod == ICAL_METHOD_COUNTER) {
  258. // dtstart contains proposal, X-MS-OLK-ORIGINALSTART optionally contains previous DTSTART
  259. lpicProp = icalcomponent_get_first_property(lpicEvent, ICAL_X_PROPERTY);
  260. while (lpicProp) {
  261. if (strcmp(icalproperty_get_x_name(lpicProp), "X-MS-OLK-ORIGINALSTART") == 0)
  262. lpicOrigDTStartProp = lpicProp;
  263. else if (strcmp(icalproperty_get_x_name(lpicProp), "X-MS-OLK-ORIGINALEND") == 0)
  264. lpicOrigDTEndProp = lpicProp;
  265. lpicProp = icalcomponent_get_next_property(lpicEvent, ICAL_X_PROPERTY);
  266. }
  267. if (lpicOrigDTStartProp && lpicOrigDTEndProp) {
  268. // No support for DTSTART +DURATION and X-MS-OLK properties. Exchange will not send that either.
  269. if (lpicDTEndProp == nullptr)
  270. return MAPI_E_INVALID_PARAMETER;
  271. // set new proposal start
  272. timeDTStartUTC = ICalTimeTypeToUTC(lpicEventRoot, lpicDTStartProp);
  273. UnixTimeToFileTime(timeDTStartUTC, &sPropVal.Value.ft);
  274. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_PROPOSEDSTART], PT_SYSTIME);
  275. lpIcalItem->lstMsgProps.push_back(sPropVal);
  276. // set new proposal end
  277. timeDTEndUTC = ICalTimeTypeToUTC(lpicEventRoot, lpicDTEndProp);
  278. UnixTimeToFileTime(timeDTEndUTC, &sPropVal.Value.ft);
  279. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_PROPOSEDEND], PT_SYSTIME);
  280. lpIcalItem->lstMsgProps.push_back(sPropVal);
  281. // rebuild properties, so libical has the right value type in the property.
  282. std::string strTmp;
  283. strTmp = icalproperty_as_ical_string_r(lpicOrigDTStartProp);
  284. strTmp.erase(0,strlen("X-MS-OLK-ORIGINAL"));
  285. strTmp.insert(0,"DT");
  286. lpicFreeDTStartProp.reset(icalproperty_new_from_string(strTmp.c_str()));
  287. lpicDTStartProp = lpicFreeDTStartProp.get();
  288. strTmp = icalproperty_as_ical_string_r(lpicOrigDTEndProp);
  289. strTmp.erase(0,strlen("X-MS-OLK-ORIGINAL"));
  290. strTmp.insert(0,"DT");
  291. lpicFreeDTEndProp.reset(icalproperty_new_from_string(strTmp.c_str()));
  292. lpicDTEndProp = lpicFreeDTEndProp.get();
  293. }
  294. }
  295. // get timezone of DTSTART
  296. timeDTStartUTC = ICalTimeTypeToUTC(lpicEventRoot, lpicDTStartProp);
  297. timeDTStartLocal = ICalTimeTypeToLocal(lpicDTStartProp);
  298. timeStartOffset = timeDTStartUTC - timeDTStartLocal;
  299. if (bIsAllday)
  300. UnixTimeToFileTime(timeDTStartLocal, &sPropVal.Value.ft);
  301. else
  302. UnixTimeToFileTime(timeDTStartUTC, &sPropVal.Value.ft);
  303. // Set 0x820D / ApptStartWhole
  304. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTSTARTWHOLE], PT_SYSTIME);
  305. lpIcalItem->lstMsgProps.push_back(sPropVal);
  306. // Set 0x8516 / CommonStart
  307. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_COMMONSTART], PT_SYSTIME);
  308. lpIcalItem->lstMsgProps.push_back(sPropVal);
  309. // Set PR_START_DATE
  310. sPropVal.ulPropTag = PR_START_DATE;
  311. lpIcalItem->lstMsgProps.push_back(sPropVal);
  312. // Set 0x8215 / AllDayEvent
  313. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_ALLDAYEVENT], PT_BOOLEAN);
  314. sPropVal.Value.b = bIsAllday;
  315. lpIcalItem->lstMsgProps.push_back(sPropVal);
  316. // Set endtime / DTEND
  317. if (lpicDTEndProp) {
  318. timeDTEndUTC = ICalTimeTypeToUTC(lpicEventRoot, lpicDTEndProp);
  319. timeDTEndLocal = ICalTimeTypeToLocal(lpicDTEndProp);
  320. } else {
  321. // @note not so sure if the following comment is 100% true. It may also be used to complement a missing DTSTART or DTEND, according to MS specs.
  322. // When DTEND is not in the ical, it should be a recurring message, which never ends!
  323. // use duration for "end"
  324. lpicDurationProp = icalcomponent_get_first_property(lpicEvent, ICAL_DURATION_PROPERTY);
  325. if (lpicDurationProp == nullptr)
  326. // and then we get here when it never ends??
  327. return MAPI_E_INVALID_PARAMETER;
  328. icaldurationtype dur = icalproperty_get_duration(lpicDurationProp);
  329. timeDTEndLocal = timeDTStartLocal + icaldurationtype_as_int(dur);
  330. timeDTEndUTC = timeDTStartUTC + icaldurationtype_as_int(dur);
  331. }
  332. timeEndOffset = timeDTEndUTC - timeDTEndLocal;
  333. if (bIsAllday)
  334. UnixTimeToFileTime(timeDTEndLocal, &sPropVal.Value.ft);
  335. else
  336. UnixTimeToFileTime(timeDTEndUTC, &sPropVal.Value.ft);
  337. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTENDWHOLE], PT_SYSTIME);
  338. lpIcalItem->lstMsgProps.push_back(sPropVal);
  339. // Set 0x8517 / CommonEnd
  340. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_COMMONEND], PT_SYSTIME);
  341. lpIcalItem->lstMsgProps.push_back(sPropVal);
  342. // Set PR_END_DATE
  343. sPropVal.ulPropTag = PR_END_DATE;
  344. lpIcalItem->lstMsgProps.push_back(sPropVal);
  345. // Set duration
  346. sPropVal.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTDURATION], PT_LONG);
  347. sPropVal.Value.ul = timeDTEndUTC - timeDTStartUTC;
  348. /**
  349. * @note This isn't what you think. The MAPI duration has a very
  350. * complicated story, when timezone compensation should be applied
  351. * and when not. This is a simplified version, which seems to work ... for now.
  352. *
  353. * See chapter 3.1.5.5 of [MS-OXOCAL].pdf for a more detailed story.
  354. */
  355. if (!bIsAllday)
  356. sPropVal.Value.ul += (timeEndOffset - timeStartOffset);
  357. // Convert from seconds to minutes.
  358. sPropVal.Value.ul /= 60;
  359. lpIcalItem->lstMsgProps.push_back(sPropVal);
  360. // @todo add flags not to add these props ?? or maybe use a exclude filter in ICalToMAPI::GetItem()
  361. // Set submit time / DTSTAMP
  362. return hrSuccess;
  363. }
  364. /**
  365. * Create a new ical VEVENT component and set all ical properties in
  366. * the returned object.
  367. *
  368. * @param[in] lpMessage The message to convert
  369. * @param[out] lpicMethod The ical method of the top VCALENDAR object (hint, can differ when mixed methods are present in one VCALENDAR)
  370. * @param[out] lppicTZinfo ical timezone struct, describes all times used in this ical component
  371. * @param[out] lpstrTZid The name of the timezone
  372. * @param[out] lppEvent The ical calendar event
  373. *
  374. * @return MAPI error code
  375. */
  376. HRESULT VEventConverter::HrMAPI2ICal(LPMESSAGE lpMessage, icalproperty_method *lpicMethod, icaltimezone **lppicTZinfo, std::string *lpstrTZid, icalcomponent **lppEvent)
  377. {
  378. icalcomp_ptr lpEvent(icalcomponent_new(ICAL_VEVENT_COMPONENT));
  379. HRESULT hr = VConverter::HrMAPI2ICal(lpMessage, lpicMethod, lppicTZinfo, lpstrTZid, lpEvent.get());
  380. if (hr != hrSuccess)
  381. return hr;
  382. if (lppEvent)
  383. *lppEvent = lpEvent.release();
  384. return hrSuccess;
  385. }
  386. /**
  387. * Extends the VConverter version to set 'appt start whole' and 'appt end whole' named properties.
  388. * This also adds the counter proposal times on a propose new time meeting request.
  389. *
  390. * @param[in] lpMsgProps All (required) properties from the message to convert time properties
  391. * @param[in] ulMsgProps number of properties in lpMsgProps
  392. * @param[in] lpicTZinfo ical timezone object to set times in
  393. * @param[in] strTZid name of the given ical timezone
  394. * @param[in,out] lpEvent The Ical object to modify
  395. *
  396. * @return MAPI error code.
  397. */
  398. HRESULT VEventConverter::HrSetTimeProperties(LPSPropValue lpMsgProps, ULONG ulMsgProps, icaltimezone *lpicTZinfo, const std::string &strTZid, icalcomponent *lpEvent)
  399. {
  400. bool bIsAllDay = false;
  401. bool bCounterProposal = false;
  402. ULONG ulStartIndex = PROP_APPTSTARTWHOLE;
  403. ULONG ulEndIndex = PROP_APPTENDWHOLE;
  404. HRESULT hr = VConverter::HrSetTimeProperties(lpMsgProps, ulMsgProps,
  405. lpicTZinfo, strTZid, lpEvent);
  406. if (hr != hrSuccess)
  407. return hr;
  408. // vevent extra
  409. auto lpPropVal = PCpropFindProp(lpMsgProps, ulMsgProps, m_lpNamedProps->aulPropTag[PROP_COUNTERPROPOSAL]);
  410. if(lpPropVal && PROP_TYPE(lpPropVal->ulPropTag) == PT_BOOLEAN && lpPropVal->Value.b) {
  411. bCounterProposal = true;
  412. ulStartIndex = PROP_PROPOSEDSTART;
  413. ulEndIndex = PROP_PROPOSEDEND;
  414. }
  415. lpPropVal = PCpropFindProp(lpMsgProps, ulMsgProps, CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_ALLDAYEVENT], PT_BOOLEAN));
  416. if (lpPropVal)
  417. bIsAllDay = (lpPropVal->Value.b == TRUE);
  418. // @note If bIsAllDay == true, the item is an allday event "in the timezone it was created in" (and the user selected the allday event option)
  419. // In another timezone, Outlook will display the item as a 24h event with times (and the allday event option disabled)
  420. // However, in ICal, you cannot specify a timezone for a date, so ICal will show this as an allday event in every timezone your client is in.
  421. // Set start time / DTSTART
  422. lpPropVal = PCpropFindProp(lpMsgProps, ulMsgProps, CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[ulStartIndex], PT_SYSTIME));
  423. if (lpPropVal == NULL)
  424. // do not create calendar items without start/end date, which is invalid.
  425. return MAPI_E_CORRUPT_DATA;
  426. time_t ttTime = FileTimeToUnixTime(lpPropVal->Value.ft.dwHighDateTime, lpPropVal->Value.ft.dwLowDateTime);
  427. hr = HrSetTimeProperty(ttTime, bIsAllDay, lpicTZinfo, strTZid, ICAL_DTSTART_PROPERTY, lpEvent);
  428. if (hr != hrSuccess)
  429. return hr;
  430. // Set end time / DTEND
  431. lpPropVal = PCpropFindProp(lpMsgProps, ulMsgProps, CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[ulEndIndex], PT_SYSTIME));
  432. if (lpPropVal == NULL) {
  433. // do not create calendar items without start/end date, which is invalid.
  434. return MAPI_E_CORRUPT_DATA;
  435. }
  436. ttTime = FileTimeToUnixTime(lpPropVal->Value.ft.dwHighDateTime, lpPropVal->Value.ft.dwLowDateTime);
  437. hr = HrSetTimeProperty(ttTime, bIsAllDay, lpicTZinfo, strTZid, ICAL_DTEND_PROPERTY, lpEvent);
  438. if (hr != hrSuccess)
  439. return hr;
  440. // @note we never set the DURATION property: MAPI objects always should have the end property
  441. if (!bCounterProposal)
  442. return hrSuccess;
  443. // set the original times in X properties
  444. icalproperty *lpProp = NULL;
  445. // Set original start time / DTSTART
  446. lpPropVal = PCpropFindProp(lpMsgProps, ulMsgProps, CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTSTARTWHOLE], PT_SYSTIME));
  447. if (lpPropVal == NULL) {
  448. // do not create calendar items without start/end date, which is invalid.
  449. return MAPI_E_CORRUPT_DATA;
  450. }
  451. ttTime = FileTimeToUnixTime(lpPropVal->Value.ft.dwHighDateTime, lpPropVal->Value.ft.dwLowDateTime);
  452. lpProp = icalproperty_new_x("overwrite-me");
  453. icalproperty_set_x_name(lpProp, "X-MS-OLK-ORIGINALSTART");
  454. hr = HrSetTimeProperty(ttTime, bIsAllDay, lpicTZinfo, strTZid, ICAL_DTSTART_PROPERTY, lpProp);
  455. if (hr != hrSuccess)
  456. return hr;
  457. icalcomponent_add_property(lpEvent, lpProp);
  458. // Set original end time / DTEND
  459. lpPropVal = PCpropFindProp(lpMsgProps, ulMsgProps, CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTENDWHOLE], PT_SYSTIME));
  460. if (lpPropVal == NULL) {
  461. // do not create calendar items without start/end date, which is invalid.
  462. return MAPI_E_CORRUPT_DATA;
  463. }
  464. ttTime = FileTimeToUnixTime(lpPropVal->Value.ft.dwHighDateTime, lpPropVal->Value.ft.dwLowDateTime);
  465. lpProp = icalproperty_new_x("overwrite-me");
  466. icalproperty_set_x_name(lpProp, "X-MS-OLK-ORIGINALEND");
  467. hr = HrSetTimeProperty(ttTime, bIsAllDay, lpicTZinfo, strTZid, ICAL_DTEND_PROPERTY, lpProp);
  468. if (hr != hrSuccess)
  469. return hr;
  470. icalcomponent_add_property(lpEvent, lpProp);
  471. return hrSuccess;
  472. }
  473. } /* namespace */