CalDavProto.cpp 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170
  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 <memory>
  19. #include <utility>
  20. #include <kopano/ECRestriction.h>
  21. #include <kopano/memory.hpp>
  22. #include <kopano/tie.hpp>
  23. #include "PublishFreeBusy.h"
  24. #include "CalDavProto.h"
  25. #include <kopano/mapi_ptr.h>
  26. #include <kopano/MAPIErrors.h>
  27. using namespace std;
  28. using namespace KCHL;
  29. /**
  30. * Maping of caldav properties to Mapi properties
  31. */
  32. static const struct sMymap {
  33. unsigned int ulPropTag;
  34. const char *name;
  35. } sPropMap[] = {
  36. { PR_LOCAL_COMMIT_TIME_MAX, "getctag" },
  37. { PR_LAST_MODIFICATION_TIME, "getetag" },
  38. { PR_DISPLAY_NAME_W, "displayname" },
  39. { PR_CONTAINER_CLASS_A, "resourcetype" },
  40. { PR_DISPLAY_NAME_W, "owner" },
  41. { PR_DISPLAY_NAME_W, "calendar-home-set" },
  42. { PR_ENTRYID, "calendar-data" },
  43. { PR_COMMENT_W, "calendar-description" },
  44. { PR_DISPLAY_TYPE, "calendar-user-type" },
  45. { PR_SMTP_ADDRESS_W, "email-address-set" },
  46. { PR_SMTP_ADDRESS_W, "calendar-user-address-set" },
  47. { PR_DISPLAY_NAME_W, "first-name" },
  48. { PR_DISPLAY_TYPE, "record-type" }
  49. };
  50. /**
  51. * Create a property tag for an XML property (namespace + name combination)
  52. *
  53. * @param[in] lpObj get the property tag from this object
  54. * @param[in] lpXmlPropName create the named prop string from this xml id
  55. * @param[in] converter a convert_context object
  56. * @param[in] ulFlags flags for GetIDsFromNames call (0 or MAPI_CREATE)
  57. *
  58. * @return the (named) property tag for the xml data, (named are set to PT_BINARY)
  59. */
  60. static ULONG GetPropIDForXMLProp(LPMAPIPROP lpObj,
  61. const WEBDAVPROPNAME &sXmlPropName, convert_context &converter,
  62. ULONG ulFlags = 0)
  63. {
  64. HRESULT hr = hrSuccess;
  65. memory_ptr<MAPINAMEID> lpNameID;
  66. SPropTagArrayPtr ptrPropTags;
  67. string strName;
  68. wstring wstrName;
  69. for (size_t i = 0; i < ARRAY_SIZE(sPropMap); ++i)
  70. // @todo, we really should use the namespace here too
  71. if (strcmp(sXmlPropName.strPropname.c_str(), sPropMap[i].name) == 0)
  72. return sPropMap[i].ulPropTag;
  73. strName = sXmlPropName.strNS + "#" + sXmlPropName.strPropname;
  74. wstrName = converter.convert_to<wstring>(strName, rawsize(strName), "UTF-8");
  75. hr = MAPIAllocateBuffer(sizeof(MAPINAMEID), &~lpNameID);
  76. if (hr != hrSuccess)
  77. return PR_NULL;
  78. lpNameID->lpguid = (GUID*)&PSETID_Kopano_CalDav;
  79. lpNameID->ulKind = MNID_STRING;
  80. lpNameID->Kind.lpwstrName = (WCHAR*)wstrName.c_str();
  81. hr = lpObj->GetIDsFromNames(1, &+lpNameID, ulFlags, &~ptrPropTags);
  82. if (hr != hrSuccess)
  83. return PR_NULL;
  84. return PROP_TAG(PT_BINARY, PROP_ID(ptrPropTags->aulPropTag[0]));
  85. }
  86. /**
  87. * @param[in] lpRequest Pointer to Http class object
  88. * @param[in] lpSession Pointer to Mapi session object
  89. * @param[in] strSrvTz String specifying the server timezone, set in ical.cfg
  90. * @param[in] strCharset String specifying the default charset of the http response
  91. */
  92. CalDAV::CalDAV(Http *lpRequest, IMAPISession *lpSession,
  93. const std::string &strSrvTz, const std::string &strCharset) :
  94. WebDav(lpRequest, lpSession, strSrvTz, strCharset)
  95. {
  96. }
  97. /**
  98. * Process all the caldav requests
  99. * @param[in] strMethod Name of the http request(e.g PROPFIND, REPORT..)
  100. * @return MAPI error code
  101. */
  102. HRESULT CalDAV::HrHandleCommand(const std::string &strMethod)
  103. {
  104. HRESULT hr = hrSuccess;
  105. if (!strMethod.compare("PROPFIND"))
  106. hr = HrPropfind();
  107. else if (!strMethod.compare("REPORT"))
  108. hr = HrReport();
  109. else if (!strMethod.compare("PUT"))
  110. hr = HrPut();
  111. else if (!strMethod.compare("DELETE"))
  112. hr = HrHandleDelete();
  113. else if (!strMethod.compare("MKCALENDAR"))
  114. hr = HrMkCalendar();
  115. else if (!strMethod.compare("PROPPATCH"))
  116. hr = HrPropPatch();
  117. else if (!strMethod.compare("POST"))
  118. hr = HrHandlePost();
  119. else if (!strMethod.compare("MOVE"))
  120. hr = HrMove();
  121. else
  122. m_lpRequest->HrResponseHeader(501, "Not Implemented");
  123. if (hr != hrSuccess)
  124. m_lpRequest->HrResponseHeader(400, "Bad Request");
  125. return hr;
  126. }
  127. /**
  128. * Handles the PROPFIND request, identifies the type of PROPFIND request
  129. *
  130. * @param[in] lpsDavProp Pointer to structure cotaining info about the PROPFIND request
  131. * @param[out] lpsDavMulStatus Response generated for the PROPFIND request
  132. * @return HRESULT
  133. */
  134. HRESULT CalDAV::HrHandlePropfind(WEBDAVREQSTPROPS *lpsDavProp, WEBDAVMULTISTATUS *lpsDavMulStatus)
  135. {
  136. HRESULT hr;
  137. ULONG ulDepth = 0;
  138. /* default depths:
  139. * caldav report: 0
  140. * webdav propfind: infinity
  141. */
  142. m_lpRequest->HrGetDepth(&ulDepth);
  143. // always load top level container properties
  144. hr = HrHandlePropfindRoot(lpsDavProp, lpsDavMulStatus);
  145. if (hr != hrSuccess)
  146. return hr;
  147. // m_wstrFldName not set means url is: /caldav/user/ so list calendars
  148. if (ulDepth == 1 && m_wstrFldName.empty())
  149. // Retrieve list of calendars
  150. return HrListCalendar(lpsDavProp, lpsDavMulStatus);
  151. else if (ulDepth >= 1)
  152. // Retrieve the Calendar entries list
  153. return HrListCalEntries(lpsDavProp, lpsDavMulStatus);
  154. return hrSuccess;
  155. }
  156. /**
  157. * Handles the Depth 0 PROPFIND request
  158. *
  159. * The client requets for information about the user,store and folder by using this request
  160. *
  161. * @param[in] sDavReqstProps Pointer to structure cotaining info about properties requested by client
  162. * @param[in] lpsDavMulStatus Pointer to structure cotaining response to the request
  163. * @return HRESULT
  164. */
  165. // @todo simplify this .. depth 0 is always on root container props.
  166. HRESULT CalDAV::HrHandlePropfindRoot(WEBDAVREQSTPROPS *sDavReqstProps, WEBDAVMULTISTATUS *lpsDavMulStatus)
  167. {
  168. HRESULT hr = hrSuccess;
  169. WEBDAVPROP *lpsDavProp = NULL;
  170. WEBDAVRESPONSE sDavResp;
  171. IMAPIProp *lpMapiProp = NULL;
  172. memory_ptr<SPropTagArray> lpPropTagArr;
  173. memory_ptr<SPropValue> lpSpropVal;
  174. ULONG cbsize = 0;
  175. int i = 0;
  176. lpsDavProp = &(sDavReqstProps->sProp);
  177. // number of properties requested by client.
  178. cbsize = lpsDavProp->lstProps.size();
  179. // @todo, we only select the store so we don't have a PR_CONTAINER_CLASS property when querying calendar list.
  180. if(m_wstrFldName.empty())
  181. lpMapiProp = m_lpActiveStore;
  182. else
  183. lpMapiProp = m_lpUsrFld;
  184. hr = MAPIAllocateBuffer(CbNewSPropTagArray(cbsize), &~lpPropTagArr);
  185. if (hr != hrSuccess)
  186. {
  187. ec_log_err("Cannot allocate memory");
  188. return hr;
  189. }
  190. lpPropTagArr->cValues = cbsize;
  191. // Get corresponding mapi properties.
  192. for (const auto &iter : lpsDavProp->lstProps)
  193. lpPropTagArr->aulPropTag[i++] = GetPropIDForXMLProp(lpMapiProp, iter.sPropName, m_converter);
  194. hr = lpMapiProp->GetProps(lpPropTagArr, 0, &cbsize, &~lpSpropVal);
  195. if (FAILED(hr)) {
  196. ec_log_err("Error in GetProps for user %ls, error code: 0x%08X %s", m_wstrUser.c_str(), hr, GetMAPIErrorMessage(hr));
  197. return hr;
  198. }
  199. hr = hrSuccess;
  200. HrSetDavPropName(&(sDavResp.sPropName), "response", WEBDAVNS);
  201. HrSetDavPropName(&(sDavResp.sHRef.sPropName), "href", WEBDAVNS);
  202. // fetches escaped url
  203. m_lpRequest->HrGetRequestUrl(&sDavResp.sHRef.strValue);
  204. // map values and properties in WEBDAVRESPONSE structure.
  205. hr = HrMapValtoStruct(lpMapiProp, lpSpropVal, cbsize, NULL, 0, false, &(lpsDavProp->lstProps), &sDavResp);
  206. if (hr != hrSuccess) {
  207. ec_log_debug("CalDAV::HrHandlePropfindRoot HrMapValtoStruct failed 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  208. return hr;
  209. }
  210. HrSetDavPropName(&(lpsDavMulStatus->sPropName), "multistatus", WEBDAVNS);
  211. lpsDavMulStatus->lstResp.push_back(std::move(sDavResp));
  212. return hrSuccess;
  213. }
  214. /**
  215. * Retrieve list of entries in the calendar folder
  216. *
  217. * The function handles REPORT(calendar-query) and PROPFIND(depth 1) request,
  218. * REPORT method is used by mozilla clients and mac ical.app uses PROPFIND request
  219. *
  220. * @param[in] lpsWebRCalQry Pointer to structure containing the list of properties requested by client
  221. * @param[out] lpsWebMStatus Pointer to structure containing the response
  222. * @return HRESULT
  223. */
  224. HRESULT CalDAV::HrListCalEntries(WEBDAVREQSTPROPS *lpsWebRCalQry, WEBDAVMULTISTATUS *lpsWebMStatus)
  225. {
  226. HRESULT hr = hrSuccess;
  227. std::string strConvVal;
  228. std::string strReqUrl;
  229. object_ptr<IMAPITable> lpTable;
  230. memory_ptr<SPropTagArray> lpPropTagArr;
  231. memory_ptr<SPropValue> lpsPropVal;
  232. std::unique_ptr<MapiToICal> lpMtIcal;
  233. ULONG cbsize = 0;
  234. ULONG ulTagGOID = 0;
  235. ULONG ulTagTsRef = 0;
  236. ULONG ulTagPrivate = 0;
  237. WEBDAVPROP sDavProp;
  238. WEBDAVRESPONSE sWebResponse;
  239. bool blCensorPrivate = false;
  240. ULONG ulCensorFlag = 0;
  241. ULONG cValues = 0;
  242. memory_ptr<SPropValue> lpProps;
  243. SPropValue sResData;
  244. ULONG ulItemCount = 0;
  245. ECOrRestriction rst;
  246. int i;
  247. ulTagGOID = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_GOID], PT_BINARY);
  248. ulTagTsRef = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTTSREF], PT_UNICODE);
  249. ulTagPrivate = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_PRIVATE], PT_BOOLEAN);
  250. m_lpRequest->HrGetRequestUrl(&strReqUrl);
  251. if (strReqUrl.empty() || *--strReqUrl.end() != '/')
  252. strReqUrl.append(1, '/');
  253. if ((m_ulFolderFlag & SHARED_FOLDER) && !HasDelegatePerm(m_lpDefStore, m_lpActiveStore))
  254. blCensorPrivate = true;
  255. HrSetDavPropName(&(sWebResponse.sPropName), "response", WEBDAVNS);
  256. HrSetDavPropName(&(sWebResponse.sHRef.sPropName), "href", WEBDAVNS);
  257. sDavProp = lpsWebRCalQry->sProp;
  258. if (!lpsWebRCalQry->sFilter.lstFilters.empty())
  259. {
  260. hr = HrGetOneProp(m_lpUsrFld, PR_CONTAINER_CLASS_A, &~lpsPropVal);
  261. if (hr != hrSuccess) {
  262. ec_log_debug("CalDAV::HrListCalEntries HrGetOneProp failed 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  263. goto exit;
  264. }
  265. if (lpsWebRCalQry->sFilter.lstFilters.back() == "VTODO"
  266. && strncmp(lpsPropVal->Value.lpszA, "IPF.Task", strlen("IPF.Task")))
  267. goto exit;
  268. if (lpsWebRCalQry->sFilter.lstFilters.back() == "VEVENT"
  269. && strncmp(lpsPropVal->Value.lpszA, "IPF.Appointment", strlen("IPF.Appointment")))
  270. goto exit;
  271. }
  272. hr = m_lpUsrFld->GetContentsTable(0, &~lpTable);
  273. if (hr != hrSuccess) {
  274. ec_log_err("Error in GetContentsTable, error code: 0x%08X %s", hr, GetMAPIErrorMessage(hr));
  275. goto exit;
  276. }
  277. // restrict on meeting requests and appointments
  278. sResData.ulPropTag = PR_MESSAGE_CLASS_A;
  279. sResData.Value.lpszA = const_cast<char *>("IPM.Appointment");
  280. rst += ECContentRestriction(FL_IGNORECASE | FL_PREFIX, PR_MESSAGE_CLASS_A, &sResData, ECRestriction::Shallow);
  281. sResData.Value.lpszA = const_cast<char *>("IPM.Meeting");
  282. rst += ECContentRestriction(FL_IGNORECASE | FL_PREFIX, PR_MESSAGE_CLASS_A, &sResData, ECRestriction::Shallow);
  283. sResData.Value.lpszA = const_cast<char *>("IPM.Task");
  284. rst += ECContentRestriction(FL_IGNORECASE | FL_PREFIX, PR_MESSAGE_CLASS_A, &sResData, ECRestriction::Shallow);
  285. hr = rst.RestrictTable(lpTable, 0);
  286. if (hr != hrSuccess) {
  287. ec_log_err("Unable to restrict folder contents, error code: 0x%08X %s", hr, GetMAPIErrorMessage(hr));
  288. goto exit;
  289. }
  290. // +4 to add GlobalObjid, dispidApptTsRef , PR_ENTRYID and private in SetColumns along with requested data.
  291. cbsize = (ULONG)sDavProp.lstProps.size() + 4;
  292. hr = MAPIAllocateBuffer(CbNewSPropTagArray(cbsize), &~lpPropTagArr);
  293. if(hr != hrSuccess)
  294. {
  295. ec_log_err("Cannot allocate memory");
  296. goto exit;
  297. }
  298. lpPropTagArr->cValues = cbsize;
  299. lpPropTagArr->aulPropTag[0] = ulTagTsRef;
  300. lpPropTagArr->aulPropTag[1] = ulTagGOID;
  301. lpPropTagArr->aulPropTag[2] = PR_ENTRYID;
  302. lpPropTagArr->aulPropTag[3] = ulTagPrivate;
  303. //mapi property mapping for requested properties.
  304. //FIXME what if the property mapping is not found.
  305. i = 4;
  306. for (const auto &sDavProperty : sDavProp.lstProps)
  307. lpPropTagArr->aulPropTag[i++] = GetPropIDForXMLProp(m_lpUsrFld, sDavProperty.sPropName, m_converter);
  308. hr = m_lpUsrFld->GetProps(lpPropTagArr, 0, &cValues, &~lpProps);
  309. if (FAILED(hr)) {
  310. ec_log_err("Unable to receive folder properties, error 0x%08X %s", hr, GetMAPIErrorMessage(hr));
  311. goto exit;
  312. }
  313. // @todo, add "start time" property and recurrence data to table and filter in loop
  314. // if lpsWebRCalQry->sFilter.tStart is set.
  315. hr = lpTable->SetColumns(lpPropTagArr, 0);
  316. if(hr != hrSuccess)
  317. goto exit;
  318. // @todo do we really need this converter, since we're only listing the items?
  319. CreateMapiToICal(m_lpAddrBook, "utf-8", &unique_tie(lpMtIcal));
  320. if (!lpMtIcal)
  321. {
  322. hr = MAPI_E_CALL_FAILED;
  323. goto exit;
  324. }
  325. while(1)
  326. {
  327. rowset_ptr lpRowSet;
  328. hr = lpTable->QueryRows(50, 0, &~lpRowSet);
  329. if(hr != hrSuccess)
  330. {
  331. ec_log_err("Error retrieving rows of table");
  332. goto exit;
  333. }
  334. if(lpRowSet->cRows == 0)
  335. break;
  336. //add data from each requested property.
  337. for (ULONG ulRowCntr = 0; ulRowCntr < lpRowSet->cRows; ++ulRowCntr)
  338. {
  339. // test PUT url part
  340. if (lpRowSet->aRow[ulRowCntr].lpProps[0].ulPropTag == ulTagTsRef)
  341. strConvVal = W2U((const WCHAR*)lpRowSet->aRow[ulRowCntr].lpProps[0].Value.lpszW);
  342. // test ical UID value
  343. else if (lpRowSet->aRow[ulRowCntr].lpProps[1].ulPropTag == ulTagGOID)
  344. strConvVal = SPropValToString(&(lpRowSet->aRow[ulRowCntr].lpProps[1]));
  345. else
  346. strConvVal.clear();
  347. // On some items, webaccess never created the uid, so we need to create one for ical
  348. if (strConvVal.empty())
  349. {
  350. // this really shouldn't happen, every item should have a guid.
  351. hr = CreateAndGetGuid(lpRowSet->aRow[ulRowCntr].lpProps[2].Value.bin, ulTagGOID, &strConvVal);
  352. if(hr == E_ACCESSDENIED)
  353. {
  354. // @todo shouldn't we use PR_ENTRYID in the first place? Saving items in a read-only command is a serious no-no.
  355. // use PR_ENTRYID since we couldn't create a new guid for the item
  356. strConvVal = bin2hex(lpRowSet->aRow[ulRowCntr].lpProps[2].Value.bin.cb, lpRowSet->aRow[ulRowCntr].lpProps[2].Value.bin.lpb);
  357. hr = hrSuccess;
  358. }
  359. else if (hr != hrSuccess) {
  360. ec_log_debug("CreateAndGetGuid failed: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  361. continue;
  362. }
  363. } else {
  364. strConvVal = urlEncode(strConvVal);
  365. }
  366. sWebResponse.sHRef.strValue = strReqUrl + strConvVal + ".ics";
  367. if (blCensorPrivate && lpRowSet->aRow[ulRowCntr].lpProps[3].ulPropTag == ulTagPrivate && lpRowSet->aRow[ulRowCntr].lpProps[3].Value.b)
  368. ulCensorFlag |= M2IC_CENSOR_PRIVATE;
  369. else
  370. ulCensorFlag = 0;
  371. hr = HrMapValtoStruct(m_lpUsrFld, lpRowSet->aRow[ulRowCntr].lpProps, lpRowSet->aRow[ulRowCntr].cValues, lpMtIcal.get(), ulCensorFlag, true, &(lpsWebRCalQry->sProp.lstProps), &sWebResponse);
  372. ++ulItemCount;
  373. lpsWebMStatus->lstResp.push_back(sWebResponse);
  374. sWebResponse.lstsPropStat.clear();
  375. }
  376. }
  377. exit:
  378. if (hr == hrSuccess)
  379. ec_log_info("Number of items in folder returned: %u", ulItemCount);
  380. return hr;
  381. }
  382. /**
  383. * Handles Report (calendar-multiget) caldav request.
  384. *
  385. * Sets values of requested caldav properties in WEBDAVMULTISTATUS structure.
  386. *
  387. * @param[in] sWebRMGet structure that contains the list of calendar entries and properties requested.
  388. * @param[out] sWebMStatus structure that values of requested properties.
  389. * @retval HRESULT
  390. * @return S_OK always returns S_OK.
  391. *
  392. */
  393. HRESULT CalDAV::HrHandleReport(WEBDAVRPTMGET *sWebRMGet, WEBDAVMULTISTATUS *sWebMStatus)
  394. {
  395. HRESULT hr = hrSuccess;
  396. object_ptr<IMAPITable> lpTable;
  397. memory_ptr<SPropTagArray> lpPropTagArr;
  398. std::unique_ptr<MapiToICal> lpMtIcal;
  399. std::string strReqUrl;
  400. memory_ptr<SRestriction> lpsRoot;
  401. ULONG cbsize = 0;
  402. WEBDAVPROP sDavProp;
  403. WEBDAVRESPONSE sWebResponse;
  404. bool blCensorPrivate = false;
  405. int i;
  406. m_lpRequest->HrGetRequestUrl(&strReqUrl);
  407. if (strReqUrl.empty() || *--strReqUrl.end() != '/')
  408. strReqUrl.append(1, '/');
  409. HrSetDavPropName(&(sWebResponse.sPropName), "response", WEBDAVNS);
  410. HrSetDavPropName(&sWebMStatus->sPropName, "multistatus", WEBDAVNS);
  411. if ((m_ulFolderFlag & SHARED_FOLDER) && !HasDelegatePerm(m_lpDefStore, m_lpActiveStore))
  412. blCensorPrivate = true;
  413. hr = m_lpUsrFld->GetContentsTable(0, &~lpTable);
  414. if(hr != hrSuccess) {
  415. ec_log_err("Error in GetContentsTable, error code: 0x%08X %s", hr, GetMAPIErrorMessage(hr));
  416. return hr;
  417. }
  418. sDavProp = sWebRMGet->sProp;
  419. //Add GUID in Setcolumns.
  420. cbsize = (ULONG)sDavProp.lstProps.size() + 2;
  421. hr = MAPIAllocateBuffer(CbNewSPropTagArray(cbsize), &~lpPropTagArr);
  422. if (hr != hrSuccess) {
  423. ec_log_err("Error allocating memory, error code: 0x%08X %s", hr, GetMAPIErrorMessage(hr));
  424. return hr;
  425. }
  426. lpPropTagArr->cValues = cbsize;
  427. lpPropTagArr->aulPropTag[0] = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_GOID], PT_BINARY);
  428. lpPropTagArr->aulPropTag[1] = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_PRIVATE], PT_BOOLEAN);
  429. i = 2;
  430. for (const auto &sDavProperty : sDavProp.lstProps)
  431. lpPropTagArr->aulPropTag[i++] = GetPropIDForXMLProp(m_lpUsrFld, sDavProperty.sPropName, m_converter);
  432. hr = lpTable->SetColumns(lpPropTagArr, 0);
  433. if(hr != hrSuccess)
  434. return hr;
  435. cbsize = (ULONG)sWebRMGet->lstWebVal.size();
  436. ec_log_info("Requesting conversion of %u items", cbsize);
  437. CreateMapiToICal(m_lpAddrBook, "utf-8", &unique_tie(lpMtIcal));
  438. if (!lpMtIcal)
  439. return MAPI_E_CALL_FAILED;
  440. for (ULONG i = 0; i < cbsize; ++i) {
  441. WEBDAVVALUE sWebDavVal;
  442. ULONG ulCensorFlag = (ULONG)blCensorPrivate;
  443. sWebDavVal = sWebRMGet->lstWebVal.front();
  444. sWebRMGet->lstWebVal.pop_front();
  445. sWebResponse.sHRef = sWebDavVal;
  446. sWebResponse.sHRef.strValue = strReqUrl + urlEncode(sWebDavVal.strValue) + ".ics";
  447. sWebResponse.sStatus = WEBDAVVALUE();
  448. hr = HrMakeRestriction(sWebDavVal.strValue, m_lpNamedProps, &~lpsRoot);
  449. if (hr != hrSuccess) {
  450. ec_log_debug("CalDAV::HrHandleReport HrMakeRestriction failed 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  451. continue;
  452. }
  453. hr = lpTable->FindRow(lpsRoot, BOOKMARK_BEGINNING, 0);
  454. if (hr != hrSuccess)
  455. ec_log_debug("Entry not found (%s), error code: 0x%08X %s", sWebDavVal.strValue.c_str(), hr, GetMAPIErrorMessage(hr));
  456. // conversion if everthing goes ok, otherwise, add empty item with failed status field
  457. // we need to return all items requested in the multistatus reply, otherwise sunbird will stop, displaying nothing to the user.
  458. rowset_ptr lpValRows;
  459. if (hr == hrSuccess) {
  460. hr = lpTable->QueryRows(1, TBL_NOADVANCE, &~lpValRows); // TODO: what if we get multiple items ?
  461. if(hr != hrSuccess || lpValRows->cRows != 1)
  462. return hr;
  463. if (blCensorPrivate && PROP_TYPE(lpValRows->aRow[0].lpProps[1].ulPropTag) != PT_ERROR && lpValRows->aRow[0].lpProps[1].Value.b)
  464. ulCensorFlag |= M2IC_CENSOR_PRIVATE;
  465. else
  466. ulCensorFlag = 0;
  467. }
  468. if(hr == hrSuccess) {
  469. hr = HrMapValtoStruct(m_lpUsrFld, lpValRows->aRow[0].lpProps, lpValRows->aRow[0].cValues, lpMtIcal.get(), ulCensorFlag, true, &sDavProp.lstProps, &sWebResponse);
  470. if (hr != hrSuccess)
  471. return hr;
  472. } else {
  473. // no: "status" can only be in <D:propstat xmlns:D="DAV:"> tag, so fix in HrMapValtoStruct
  474. HrSetDavPropName(&(sWebResponse.sStatus.sPropName), "status", WEBDAVNS);
  475. sWebResponse.sStatus.strValue = "HTTP/1.1 404 Not Found";
  476. }
  477. sWebMStatus->lstResp.push_back(sWebResponse);
  478. sWebResponse.lstsPropStat.clear();
  479. }
  480. return hrSuccess;
  481. }
  482. /**
  483. * Generates response to Property search set request
  484. *
  485. * The request is to list the properties that can be requested by client,
  486. * while requesting for attendee suggestions
  487. *
  488. * @param[out] lpsWebMStatus Response structure returned
  489. *
  490. * @return HRESULT Always returns hrSuccess
  491. */
  492. HRESULT CalDAV::HrHandlePropertySearchSet(WEBDAVMULTISTATUS *lpsWebMStatus)
  493. {
  494. HRESULT hr = hrSuccess;
  495. WEBDAVRESPONSE sDavResponse;
  496. WEBDAVPROPSTAT sDavPropStat;
  497. WEBDAVPROPERTY sWebProperty;
  498. WEBDAVPROP sWebProp;
  499. HrSetDavPropName(&(lpsWebMStatus->sPropName), "principal-search-property-set", WEBDAVNS);
  500. HrSetDavPropName(&sDavResponse.sPropName, "principal-search-property", WEBDAVNS);
  501. HrSetDavPropName(&sDavPropStat.sPropName, "prop", WEBDAVNS);
  502. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "displayname", WEBDAVNS);
  503. sDavResponse.lstsPropStat.push_back(sDavPropStat);
  504. HrSetDavPropName(&sDavResponse.sHRef.sPropName, "description", "xml:lang", "en", WEBDAVNS);
  505. sDavResponse.sHRef.strValue = "Display Name";
  506. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "", "");
  507. lpsWebMStatus->lstResp.push_back(sDavResponse);
  508. sDavResponse.lstsPropStat.clear();
  509. HrSetDavPropName(&sDavResponse.sPropName, "principal-search-property", WEBDAVNS);
  510. HrSetDavPropName(&sDavPropStat.sPropName, "prop", WEBDAVNS);
  511. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "calendar-user-type", WEBDAVNS);
  512. sDavResponse.lstsPropStat.push_back(sDavPropStat);
  513. HrSetDavPropName(&sDavResponse.sHRef.sPropName, "description", "xml:lang", "en", WEBDAVNS);
  514. sDavResponse.sHRef.strValue = "Calendar user type";
  515. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "", "");
  516. lpsWebMStatus->lstResp.push_back(sDavResponse);
  517. sDavResponse.lstsPropStat.clear();
  518. HrSetDavPropName(&sDavResponse.sPropName, "principal-search-property", WEBDAVNS);
  519. HrSetDavPropName(&sDavPropStat.sPropName, "prop", WEBDAVNS);
  520. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "calendar-user-address-set", WEBDAVNS);
  521. sDavResponse.lstsPropStat.push_back(sDavPropStat);
  522. HrSetDavPropName(&sDavResponse.sHRef.sPropName, "description", "xml:lang", "en", WEBDAVNS);
  523. sDavResponse.sHRef.strValue = "Calendar User Address Set";
  524. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "", "");
  525. lpsWebMStatus->lstResp.push_back(sDavResponse);
  526. sDavResponse.lstsPropStat.clear();
  527. HrSetDavPropName(&sDavResponse.sPropName, "principal-search-property", WEBDAVNS);
  528. HrSetDavPropName(&sDavPropStat.sPropName, "prop", WEBDAVNS);
  529. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "email-address-set", "http://calendarserver.org/ns/");
  530. sDavResponse.lstsPropStat.push_back(sDavPropStat);
  531. HrSetDavPropName(&sDavResponse.sHRef.sPropName, "description", "xml:lang", "en", WEBDAVNS);
  532. sDavResponse.sHRef.strValue = "Email Address";
  533. HrSetDavPropName(&sDavPropStat.sProp.sPropName, "", "");
  534. lpsWebMStatus->lstResp.push_back(sDavResponse);
  535. sDavResponse.lstsPropStat.clear();
  536. return hr;
  537. }
  538. /**
  539. * Handles attendee suggestion list request
  540. *
  541. * @param[in] sWebRMGet Pointer to WEBDAVRPTMGET structure containing user to search in global address book
  542. * @param[out] sWebMStatus Pointer to WEBDAVMULTISTATUS structure cotaning attndees list matching the request
  543. *
  544. * @return HRESULT Always returns hrSuccess
  545. */
  546. HRESULT CalDAV::HrHandlePropertySearch(WEBDAVRPTMGET *sWebRMGet, WEBDAVMULTISTATUS *sWebMStatus)
  547. {
  548. HRESULT hr = hrSuccess;
  549. object_ptr<IABContainer> lpAbCont;
  550. object_ptr<IMAPITable> lpTable;
  551. memory_ptr<SPropTagArray> lpPropTagArr;
  552. ULONG cbsize = 0;
  553. ULONG ulPropTag = 0;
  554. ULONG ulTagPrivate = 0;
  555. std::list<WEBDAVVALUE>::const_iterator iterWebVal;
  556. SBinary sbEid = {0, NULL};
  557. WEBDAVPROP sDavProp;
  558. WEBDAVRESPONSE sWebResponse;
  559. ULONG ulObjType = 0;
  560. std::string strReq;
  561. ECOrRestriction rst;
  562. int i;
  563. m_lpRequest->HrGetRequestUrl(&strReq);
  564. // Open Global Address book
  565. hr = m_lpAddrBook->GetDefaultDir(&sbEid.cb, (LPENTRYID*)&sbEid.lpb);
  566. if (hr != hrSuccess) {
  567. ec_log_debug("CalDAV::HrHandlePropertySearch GetDefaultDir failed: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  568. goto exit;
  569. }
  570. hr = m_lpSession->OpenEntry(sbEid.cb, reinterpret_cast<ENTRYID *>(sbEid.lpb), nullptr, 0, &ulObjType, &~lpAbCont);
  571. if (hr != hrSuccess) {
  572. ec_log_debug("CalDAV::HrHandlePropertySearch OpenEntry failed: 0x%08x %s", hr, GetMAPIErrorMessage(hr));
  573. goto exit;
  574. }
  575. hr = lpAbCont->GetContentsTable(0, &~lpTable);
  576. if (hr != hrSuccess) {
  577. ec_log_debug("CalDAV::HrHandlePropertySearch GetContentsTable failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  578. goto exit;
  579. }
  580. hr = lpTable->GetRowCount(0, &ulObjType);
  581. if (hr != hrSuccess) {
  582. ec_log_debug("CalDAV::HrHandlePropertySearch GetRowCount failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  583. goto exit;
  584. }
  585. // create restriction
  586. iterWebVal = sWebRMGet->lstWebVal.cbegin();
  587. for (size_t i = 0; i < sWebRMGet->lstWebVal.size(); ++i, ++iterWebVal) {
  588. wstring content = U2W(iterWebVal->strValue);
  589. SPropValue pv;
  590. pv.ulPropTag = GetPropIDForXMLProp(lpAbCont, iterWebVal->sPropName, m_converter);
  591. pv.Value.lpszW = const_cast<wchar_t *>(content.c_str());
  592. rst += ECContentRestriction(FL_SUBSTRING | FL_IGNORECASE, pv.ulPropTag, &pv, ECRestriction::Full);
  593. }
  594. // create proptagarray.
  595. sDavProp = sWebRMGet->sProp;
  596. //Add GUID in Setcolumns.
  597. cbsize = (ULONG)sDavProp.lstProps.size() + 3;
  598. hr = MAPIAllocateBuffer(CbNewSPropTagArray(cbsize), &~lpPropTagArr);
  599. if (hr != hrSuccess) {
  600. ec_log_err("Error allocating memory, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  601. goto exit;
  602. }
  603. ulTagPrivate = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_PRIVATE], PT_BOOLEAN);
  604. ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_GOID], PT_BINARY);
  605. lpPropTagArr->cValues = cbsize;
  606. lpPropTagArr->aulPropTag[0] = ulPropTag;
  607. lpPropTagArr->aulPropTag[1] = ulTagPrivate;
  608. lpPropTagArr->aulPropTag[2] = PR_ACCOUNT;
  609. i = 3;
  610. for (const auto &sDavProperty : sDavProp.lstProps)
  611. lpPropTagArr->aulPropTag[i++] = GetPropIDForXMLProp(lpAbCont, sDavProperty.sPropName, m_converter);
  612. hr = lpTable->SetColumns(lpPropTagArr, 0);
  613. if (hr != hrSuccess) {
  614. ec_log_debug("CalDAV::HrHandlePropertySearch SetColumns failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  615. goto exit;
  616. }
  617. // restrict table
  618. hr = rst.RestrictTable(lpTable, 0);
  619. if (hr != hrSuccess) {
  620. ec_log_debug("CalDAV::HrHandlePropertySearch restrict failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  621. goto exit;
  622. }
  623. hr = lpTable->GetRowCount(0, &ulObjType);
  624. if (hr != hrSuccess) {
  625. ec_log_debug("CalDAV::HrHandlePropertySearch getrowcount failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  626. goto exit;
  627. }
  628. HrSetDavPropName(&(sWebResponse.sPropName), "response", WEBDAVNS);
  629. HrSetDavPropName(&sWebMStatus->sPropName, "multistatus", WEBDAVNS);
  630. // set rows into Dav structures
  631. while (1) {
  632. rowset_ptr lpValRows;
  633. hr = lpTable->QueryRows(50, 0, &~lpValRows); // TODO: what if we get multiple items ?
  634. if (hr != hrSuccess || lpValRows->cRows == 0)
  635. break;
  636. for (ULONG i = 0; i < lpValRows->cRows; ++i) {
  637. WEBDAVVALUE sWebDavVal;
  638. auto lpsPropVal = PCpropFindProp(lpValRows->aRow[i].lpProps, lpValRows->aRow[i].cValues, PR_ACCOUNT_W);
  639. if (!lpsPropVal)
  640. continue; // user without account name is useless
  641. HrSetDavPropName(&(sWebResponse.sHRef.sPropName), "href", WEBDAVNS);
  642. sWebResponse.sHRef.strValue = strReq + urlEncode(lpsPropVal->Value.lpszW, "utf-8") + "/";
  643. hr = HrMapValtoStruct(lpAbCont, lpValRows->aRow[i].lpProps, lpValRows->aRow[i].cValues, NULL, 0, true, &sDavProp.lstProps, &sWebResponse);
  644. if (hr != hrSuccess) {
  645. ec_log_err("Unable to convert user properties to entry for user %ls", lpsPropVal->Value.lpszW);
  646. continue;
  647. }
  648. sWebMStatus->lstResp.push_back(sWebResponse);
  649. sWebResponse.lstsPropStat.clear();
  650. }
  651. }
  652. hr = hrSuccess;
  653. exit:
  654. MAPIFreeBuffer(sbEid.lpb);
  655. return hr;
  656. }
  657. /**
  658. * Function moves a folder or message to the deleted items folder
  659. * @note does not check if-match: if you had a message modified which you now want to delete, we delete anyway
  660. *
  661. * @return mapi error code
  662. * @retval MAPI_E_NO_ACCESS Insufficient permissions on folder or request to delete default folder.
  663. * @retval MAPI_E_NOT_FOUND Message or folder to be deleted not found.
  664. */
  665. HRESULT CalDAV::HrHandleDelete()
  666. {
  667. HRESULT hr = hrSuccess;
  668. int nFldId = 1;
  669. std::string strGuid;
  670. std::string strUrl;
  671. std::wstring wstrFldName;
  672. std::wstring wstrFldTmpName;
  673. SBinary sbEid = {0,0};
  674. ULONG ulObjType = 0;
  675. ULONG cValues = 0;
  676. object_ptr<IMAPIFolder> lpWastBoxFld;
  677. memory_ptr<SPropValue> lpProps, lpPropWstBxEID;
  678. memory_ptr<ENTRYLIST> lpEntryList;
  679. bool bisFolder = false;
  680. static constexpr const SizedSPropTagArray(3, lpPropTagArr) =
  681. {3, {PR_ENTRYID, PR_LAST_MODIFICATION_TIME, PR_DISPLAY_NAME_W}};
  682. m_lpRequest->HrGetUrl(&strUrl);
  683. bisFolder = m_ulUrlFlag & REQ_COLLECTION;
  684. // deny delete of default folder
  685. if (!m_blFolderAccess && bisFolder)
  686. {
  687. hr = MAPI_E_NO_ACCESS;
  688. goto exit;
  689. }
  690. hr = HrGetOneProp(m_lpDefStore, PR_IPM_WASTEBASKET_ENTRYID, &~lpPropWstBxEID);
  691. if(hr != hrSuccess) {
  692. ec_log_err("Error finding \"Deleted items\" folder, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  693. goto exit;
  694. }
  695. hr = m_lpDefStore->OpenEntry(lpPropWstBxEID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpPropWstBxEID->Value.bin.lpb), nullptr, MAPI_MODIFY, &ulObjType, &~lpWastBoxFld);
  696. if (hr != hrSuccess)
  697. {
  698. ec_log_err("Error opening \"Deleted items\" folder, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  699. goto exit;
  700. }
  701. // if the request is to delete calendar entry
  702. if (!bisFolder) {
  703. strGuid = StripGuid(strUrl);
  704. hr = HrMoveEntry(strGuid, lpWastBoxFld);
  705. goto exit;
  706. }
  707. hr = m_lpUsrFld->GetProps(lpPropTagArr, 0, &cValues, &~lpProps);
  708. if (FAILED(hr)) {
  709. ec_log_debug("CalDAV::HrHandleDelete getprops failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  710. goto exit;
  711. }
  712. // Entry ID
  713. if (lpProps[0].ulPropTag != PT_ERROR)
  714. sbEid = lpProps[0].Value.bin;
  715. // Folder display name
  716. if (lpProps[2].ulPropTag != PT_ERROR)
  717. wstrFldName = lpProps[2].Value.lpszW;
  718. //Create Entrylist
  719. hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~lpEntryList);
  720. if (hr != hrSuccess) {
  721. ec_log_debug("CalDAV::HrHandleDelete mapiallocatebuffer failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  722. goto exit;
  723. }
  724. lpEntryList->cValues = 1;
  725. hr = MAPIAllocateMore(sizeof(SBinary), lpEntryList, (void**)&lpEntryList->lpbin);
  726. if (hr != hrSuccess) {
  727. ec_log_debug("CalDAV::HrHandleDelete mapiallocatemore failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  728. goto exit;
  729. }
  730. hr = MAPIAllocateMore(sbEid.cb, lpEntryList, (void**)&lpEntryList->lpbin[0].lpb);
  731. if (hr != hrSuccess) {
  732. ec_log_debug("CalDAV::HrHandleDelete mapiallocatemore(2) failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  733. goto exit;
  734. }
  735. lpEntryList->lpbin[0].cb = sbEid.cb;
  736. memcpy(lpEntryList->lpbin[0].lpb, sbEid.lpb, sbEid.cb);
  737. wstrFldTmpName = wstrFldName;
  738. while (true) {
  739. hr = m_lpIPMSubtree->CopyFolder(sbEid.cb, (LPENTRYID)sbEid.lpb, NULL, lpWastBoxFld, (LPTSTR)wstrFldTmpName.c_str(), 0, NULL, MAPI_MOVE | MAPI_UNICODE);
  740. if (hr == MAPI_E_COLLISION) {
  741. // rename the folder if same folder name is present in Deleted items folder
  742. if (nFldId >= 1000) { // Max 999 folders
  743. ec_log_err("Error Deleting Folder error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  744. goto exit;
  745. }
  746. wstrFldTmpName = wstrFldName + std::to_wstring(nFldId);
  747. ++nFldId;
  748. } else if (hr != hrSuccess ) {
  749. ec_log_err("Error Deleting Folder error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  750. goto exit;
  751. } else
  752. break;
  753. }
  754. exit:
  755. if (hr == MAPI_E_NO_ACCESS)
  756. {
  757. m_lpRequest->HrResponseHeader(403, "Forbidden");
  758. m_lpRequest->HrResponseBody("This item cannot be deleted");
  759. }
  760. else if (hr != hrSuccess)
  761. {
  762. m_lpRequest->HrResponseHeader(404, "Not Found");
  763. m_lpRequest->HrResponseBody("Item to be Deleted not found");
  764. }
  765. else
  766. m_lpRequest->HrResponseHeader(204, "No Content");
  767. return hr;
  768. }
  769. /**
  770. * Moves calendar entry to destination folder
  771. *
  772. * Function searches for the calendar refrenced by the guid value in the
  773. * folder opened by HrGetFolder() and moves the entry to the destination folder.
  774. *
  775. * @param[in] strGuid The Guid refrencing a calendar entry
  776. * @param[in] lpDestFolder The destination folder to which the entry is moved.
  777. *
  778. * @return mapi error codes
  779. *
  780. * @retval MAPI_E_NOT_FOUND No message found containing the guid value
  781. * @retval MAPI_E_NO_ACCESS Insufficient rights on the calendar entry
  782. *
  783. * @todo Check folder type and message type are same(i.e tasks are copied to task folder only)
  784. */
  785. HRESULT CalDAV::HrMoveEntry(const std::string &strGuid, LPMAPIFOLDER lpDestFolder)
  786. {
  787. HRESULT hr = hrSuccess;
  788. SBinary sbEid = {0,0};
  789. memory_ptr<SPropValue> lpProps;
  790. object_ptr<IMessage> lpMessage;
  791. memory_ptr<ENTRYLIST> lpEntryList;
  792. bool bMatch = false;
  793. //Find Entry With Particular Guid
  794. hr = HrFindAndGetMessage(strGuid, m_lpUsrFld, m_lpNamedProps, &~lpMessage);
  795. if (hr != hrSuccess)
  796. {
  797. ec_log_err("Entry to be deleted not found: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  798. return hr;
  799. }
  800. bMatch = ! m_lpRequest->CheckIfMatch(lpMessage);
  801. if (bMatch)
  802. return MAPI_E_DECLINE_COPY;
  803. // Check if the user is accessing a shared folder
  804. // Check for delegate permissions on shared folder
  805. // Check if the entry to be deleted in private
  806. if ((m_ulFolderFlag & SHARED_FOLDER) &&
  807. !HasDelegatePerm(m_lpDefStore, m_lpActiveStore) &&
  808. IsPrivate(lpMessage, CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_PRIVATE], PT_BOOLEAN)) )
  809. return MAPI_E_NO_ACCESS;
  810. hr = HrGetOneProp(lpMessage, PR_ENTRYID, &~lpProps);
  811. if (hr != hrSuccess)
  812. return hr;
  813. sbEid = lpProps[0].Value.bin;
  814. //Create Entrylist
  815. hr = MAPIAllocateBuffer(sizeof(ENTRYLIST), &~lpEntryList);
  816. if (hr != hrSuccess) {
  817. ec_log_debug("CalDAV::HrMoveEntry MAPIAllocateBuffer failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  818. return hr;
  819. }
  820. lpEntryList->cValues = 1;
  821. hr = MAPIAllocateMore(sizeof(SBinary), lpEntryList, (void**)&lpEntryList->lpbin);
  822. if (hr != hrSuccess) {
  823. ec_log_debug("CalDAV::HrMoveEntry MAPIAllocateMore failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  824. return hr;
  825. }
  826. hr = MAPIAllocateMore(sbEid.cb, lpEntryList, (void**)&lpEntryList->lpbin[0].lpb);
  827. if (hr != hrSuccess) {
  828. ec_log_debug("CalDAV::HrMoveEntry MAPIAllocateMore(2) failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  829. return hr;
  830. }
  831. lpEntryList->lpbin[0].cb = sbEid.cb;
  832. memcpy(lpEntryList->lpbin[0].lpb, sbEid.lpb, sbEid.cb);
  833. hr = m_lpUsrFld->CopyMessages(lpEntryList, NULL, lpDestFolder, 0, NULL, MAPI_MOVE);
  834. if (hr != hrSuccess)
  835. {
  836. ec_log_err("Error Deleting Entry");
  837. return hr;
  838. }
  839. // publish freebusy for default folder
  840. if (m_ulFolderFlag & DEFAULT_FOLDER)
  841. hr = HrPublishDefaultCalendar(m_lpSession, m_lpDefStore, time(NULL), FB_PUBLISH_DURATION);
  842. if (hr != hrSuccess)
  843. ec_log_err("Error Publishing Freebusy, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  844. return hrSuccess;
  845. }
  846. /**
  847. * Handles adding new entries and modify existing entries in folder
  848. *
  849. * @return HRESULT
  850. * @retval MAPI_E_INVALID_PARAMETER invalid folder specified in URL
  851. * @retval MAPI_E_NO_ACCESS insufficient permissions on folder or message
  852. * @retval MAPI_E_INVALID_OBJECT no message in ical data.
  853. */
  854. HRESULT CalDAV::HrPut()
  855. {
  856. HRESULT hr = hrSuccess;
  857. std::string strGuid;
  858. std::string strUrl;
  859. std::string strIcal;
  860. std::string strIfMatch;
  861. SPropValuePtr ptrPropModTime;
  862. memory_ptr<SPropValue> lpsPropVal;
  863. eIcalType etype = VEVENT;
  864. SBinary sbUid;
  865. time_t ttLastModTime = 0;
  866. object_ptr<IMessage> lpMessage;
  867. ICalToMapi *lpICalToMapi = NULL;
  868. bool blNewEntry = false;
  869. bool bMatch = false;
  870. m_lpRequest->HrGetUrl(&strUrl);
  871. strGuid = StripGuid(strUrl);
  872. //Find the Entry with particular Guid
  873. hr = HrFindAndGetMessage(strGuid, m_lpUsrFld, m_lpNamedProps, &~lpMessage);
  874. if(hr == hrSuccess)
  875. {
  876. // check if entry can be modified by the user
  877. // check if the user is accessing shared folder
  878. // check if delegate permissions
  879. // check if the entry is private
  880. if ( m_ulFolderFlag & SHARED_FOLDER &&
  881. !HasDelegatePerm(m_lpDefStore, m_lpActiveStore) &&
  882. IsPrivate(lpMessage, CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_PRIVATE], PT_BOOLEAN)) )
  883. {
  884. hr = MAPI_E_NO_ACCESS;
  885. goto exit;
  886. }
  887. } else {
  888. SPropValue sProp;
  889. blNewEntry = true;
  890. hr = m_lpUsrFld->CreateMessage(nullptr, 0, &~lpMessage);
  891. if (hr != hrSuccess) {
  892. ec_log_err("Error creating new message, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  893. goto exit;
  894. }
  895. // we need to be able to find the message under the url that was used in the PUT
  896. // PUT /caldav/user/folder/item.ics
  897. // GET /caldav/user/folder/item.ics
  898. // and item.ics has UID:unrelated, the above urls should work, so we save the item part in a custom tag.
  899. sProp.ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_APPTTSREF], PT_STRING8);
  900. sProp.Value.lpszA = (char*)strGuid.c_str();
  901. hr = HrSetOneProp(lpMessage, &sProp);
  902. if (hr != hrSuccess) {
  903. ec_log_err("Error adding property to new message, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  904. goto exit;
  905. }
  906. }
  907. bMatch = ! m_lpRequest->CheckIfMatch(lpMessage);
  908. if (bMatch)
  909. goto exit;
  910. //save Ical data to mapi.
  911. CreateICalToMapi(m_lpActiveStore, m_lpAddrBook, false, &lpICalToMapi);
  912. m_lpRequest->HrGetBody(&strIcal);
  913. hr = lpICalToMapi->ParseICal(strIcal, m_strCharset, m_strSrvTz, m_lpLoginUser, 0);
  914. if(hr!=hrSuccess)
  915. {
  916. ec_log_err("Error Parsing ical data in PUT request, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  917. ec_log_debug("Error Parsing ical data: %s", strIcal.c_str());
  918. goto exit;
  919. }
  920. if (lpICalToMapi->GetItemCount() == 0)
  921. {
  922. hr = MAPI_E_INVALID_OBJECT;
  923. ec_log_err("No message in ical data in PUT request, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  924. goto exit;
  925. }
  926. if (lpICalToMapi->GetItemCount() > 1)
  927. ec_log_warn("More than one message found in PUT, trying to combine messages");
  928. hr = HrGetOneProp(m_lpUsrFld, PR_CONTAINER_CLASS_A, &~lpsPropVal);
  929. if (hr != hrSuccess) {
  930. ec_log_debug("CalDAV::HrPut get property PR_CONTAINER_CLASS_A failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  931. goto exit;
  932. }
  933. hr = lpICalToMapi->GetItemInfo(0, &etype, &ttLastModTime, &sbUid);
  934. if (hr != hrSuccess) {
  935. ec_log_debug("CalDAV::HrPut no access(1) 0x%x %s", hr, GetMAPIErrorMessage(hr));
  936. hr = MAPI_E_NO_ACCESS;
  937. goto exit;
  938. }
  939. // FIXME: fix the check
  940. if ((etype == VEVENT && strncmp(lpsPropVal->Value.lpszA, "IPF.Appointment", strlen("IPF.Appointment")))
  941. || (etype == VTODO && strncmp(lpsPropVal->Value.lpszA, "IPF.Task", strlen("IPF.Task"))))
  942. {
  943. ec_log_debug("CalDAV::HrPut no access(2) 0x%x %s", hr, GetMAPIErrorMessage(hr));
  944. hr = MAPI_E_NO_ACCESS;
  945. goto exit;
  946. }
  947. hr = lpICalToMapi->GetItem(0, 0, lpMessage);
  948. if(hr != hrSuccess)
  949. {
  950. ec_log_err("Error converting ical data to Mapi message in PUT request, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  951. goto exit;
  952. }
  953. // get other messages if present
  954. for (ULONG n = 1; n < lpICalToMapi->GetItemCount(); ++n) {
  955. SBinary sbSubUid;
  956. eIcalType eSubType = VEVENT;
  957. hr = lpICalToMapi->GetItemInfo(n, &eSubType, NULL, &sbSubUid);
  958. if (hr != hrSuccess) {
  959. ec_log_debug("CalDAV::HrPut no access(3) 0x%x %s", hr, GetMAPIErrorMessage(hr));
  960. hr = MAPI_E_NO_ACCESS;
  961. goto exit;
  962. }
  963. if (etype != eSubType || Util::CompareSBinary(sbUid, sbSubUid) != hrSuccess) {
  964. hr = MAPI_E_INVALID_OBJECT;
  965. ec_log_err("Invalid sub item in ical, unable to save message");
  966. goto exit;
  967. }
  968. // merge in the same message, and hope for the best
  969. hr = lpICalToMapi->GetItem(n, 0, lpMessage);
  970. if(hr != hrSuccess)
  971. {
  972. ec_log_err("Error converting ical data to Mapi message in PUT request, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  973. goto exit;
  974. }
  975. }
  976. hr = lpMessage->SaveChanges(0);
  977. if (hr != hrSuccess) {
  978. ec_log_err("Error saving Mapi message in PUT request, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  979. goto exit;
  980. }
  981. // new modification time
  982. if (HrGetOneProp(lpMessage, PR_LAST_MODIFICATION_TIME, &~ptrPropModTime) == hrSuccess)
  983. m_lpRequest->HrResponseHeader("Etag", SPropValToString(ptrPropModTime));
  984. // Publish freebusy only for default Calendar
  985. if (m_ulFolderFlag & DEFAULT_FOLDER &&
  986. HrPublishDefaultCalendar(m_lpSession, m_lpDefStore, time(NULL), FB_PUBLISH_DURATION) != hrSuccess)
  987. // @todo already logged, since we pass the logger in the publish function?
  988. ec_log_err("Error Publishing Freebusy, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  989. exit:
  990. if (hr == hrSuccess && blNewEntry)
  991. m_lpRequest->HrResponseHeader(201, "Created");
  992. else if (hr == hrSuccess && bMatch)
  993. m_lpRequest->HrResponseHeader(412, "Precondition failed");
  994. else if (hr == hrSuccess)
  995. m_lpRequest->HrResponseHeader(204, "No Content");
  996. else if (hr == MAPI_E_NOT_FOUND)
  997. m_lpRequest->HrResponseHeader(404, "Not Found");
  998. else if (hr == MAPI_E_NO_ACCESS)
  999. m_lpRequest->HrResponseHeader(403, "Forbidden");
  1000. else
  1001. m_lpRequest->HrResponseHeader(400, "Bad Request");
  1002. delete lpICalToMapi;
  1003. return hr;
  1004. }
  1005. /**
  1006. * Creates a new guid in the message and returns it
  1007. *
  1008. * @param[in] sbEid EntryID of the message
  1009. * @param[in] ulPropTag Property tag of the Guid property
  1010. * @param[out] lpstrGuid The newly created guid is returned
  1011. *
  1012. * @return HRESULT
  1013. */
  1014. HRESULT CalDAV::CreateAndGetGuid(SBinary sbEid, ULONG ulPropTag, std::string *lpstrGuid)
  1015. {
  1016. HRESULT hr = hrSuccess;
  1017. string strGuid;
  1018. object_ptr<IMessage> lpMessage;
  1019. ULONG ulObjType = 0;
  1020. memory_ptr<SPropValue> lpProp;
  1021. hr = m_lpActiveStore->OpenEntry(sbEid.cb, reinterpret_cast<ENTRYID *>(sbEid.lpb), nullptr, MAPI_BEST_ACCESS, &ulObjType, &~lpMessage);
  1022. if (hr != hrSuccess) {
  1023. ec_log_err("Error opening message to add Guid, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1024. return hr;
  1025. }
  1026. hr = HrCreateGlobalID(ulPropTag, NULL, &~lpProp);
  1027. if (hr != hrSuccess) {
  1028. ec_log_err("Error creating Guid, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1029. return hr;
  1030. }
  1031. hr = lpMessage->SetProps(1, lpProp, NULL);
  1032. if (hr != hrSuccess) {
  1033. ec_log_err("Error while adding Guid to message, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1034. return hr;
  1035. }
  1036. hr = lpMessage->SaveChanges(0);
  1037. if (hr != hrSuccess) {
  1038. ec_log_debug("CalDAV::CreateAndGetGuid SaveChanges failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1039. return hr;
  1040. }
  1041. *lpstrGuid = bin2hex(lpProp->Value.bin.cb, lpProp->Value.bin.lpb);
  1042. return hrSuccess;
  1043. }
  1044. /**
  1045. * Creates new calendar folder
  1046. *
  1047. * @param[in] lpsDavProp Pointer to WEBDAVPROP structure, contains properite to be set on new folder
  1048. * @return HRESULT
  1049. * @retval MAPI_E_NO_ACCESS Unable to create folder, while accessing single calendar
  1050. * @retval MAPI_E_COLLISION Folder with same name already exists
  1051. */
  1052. HRESULT CalDAV::HrHandleMkCal(WEBDAVPROP *lpsDavProp)
  1053. {
  1054. HRESULT hr = hrSuccess;
  1055. std::wstring wstrNewFldName;
  1056. object_ptr<IMAPIFolder> lpUsrFld;
  1057. SPropValue sPropValSet[2];
  1058. ULONG ulPropTag = 0;
  1059. std::string strContainerClass = "IPF.Appointment";
  1060. // @todo handle other props as in proppatch command
  1061. for (const auto &p : lpsDavProp->lstProps) {
  1062. if (p.sPropName.strPropname.compare("displayname") == 0) {
  1063. wstrNewFldName = U2W(p.strValue);
  1064. continue;
  1065. }
  1066. if (p.sPropName.strPropname.compare("supported-calendar-component-set") != 0)
  1067. continue;
  1068. if (p.strValue.compare("VTODO") == 0)
  1069. strContainerClass = "IPF.Task";
  1070. else if (p.strValue.compare("VEVENT") != 0) {
  1071. ec_log_err("Unable to create folder for supported-calendar-component-set type: %s", p.strValue.c_str());
  1072. return MAPI_E_INVALID_PARAMETER;
  1073. }
  1074. }
  1075. if (wstrNewFldName.empty())
  1076. return MAPI_E_COLLISION;
  1077. // @todo handle conflicts better. caldav conflicts on the url (guid), not the folder name...
  1078. hr = m_lpIPMSubtree->CreateFolder(FOLDER_GENERIC, (LPTSTR)wstrNewFldName.c_str(), nullptr, nullptr, MAPI_UNICODE, &~lpUsrFld);
  1079. if (hr != hrSuccess) {
  1080. ec_log_debug("CalDAV::HrHandleMkCal create folder failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1081. return hr;
  1082. }
  1083. sPropValSet[0].ulPropTag = PR_CONTAINER_CLASS_A;
  1084. sPropValSet[0].Value.lpszA = (char*)strContainerClass.c_str();
  1085. sPropValSet[1].ulPropTag = PR_COMMENT_A;
  1086. sPropValSet[1].Value.lpszA = const_cast<char *>("Created by CalDAV Gateway");
  1087. hr = lpUsrFld->SetProps(2, sPropValSet, NULL);
  1088. if (hr != hrSuccess) {
  1089. ec_log_debug("CalDAV::HrHandleMkCal SetProps failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1090. return hr;
  1091. }
  1092. ulPropTag = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_FLDID], PT_UNICODE);
  1093. // saves the url name (guid) into the guid named property, @todo fix function name to reflect action better
  1094. hr = HrAddProperty(lpUsrFld, ulPropTag, true, &m_wstrFldName);
  1095. if(hr != hrSuccess) {
  1096. ec_log_err("Cannot Add named property, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1097. return hr;
  1098. }
  1099. // @todo set all xml properties as named properties on this folder
  1100. return hrSuccess;
  1101. }
  1102. /**
  1103. * Retrieves the list of calendar and sets it in WEBDAVMULTISTATUS structure
  1104. *
  1105. * @param[in] sDavProp Pointer to requested properties of folders
  1106. * @param[out] lpsMulStatus Pointer to WEBDAVMULTISTATUS structure, the calendar list and its properties are set in this structure
  1107. *
  1108. * @return HRESULT
  1109. * @retval MAPI_E_BAD_VALUE Method called by a non mac client
  1110. */
  1111. /*
  1112. * input output
  1113. * /caldav list of /caldav/user/FLDPRFX_id
  1114. * /caldav/other list of /caldav/other/FLDPRFX_id
  1115. * /caldav/other/folder NO! should not have been called
  1116. * /caldav/public list of /caldav/public/FLDPRFX_id
  1117. * /caldav/public/folder NO! should not have been called
  1118. */
  1119. HRESULT CalDAV::HrListCalendar(WEBDAVREQSTPROPS *sDavProp, WEBDAVMULTISTATUS *lpsMulStatus)
  1120. {
  1121. HRESULT hr = hrSuccess;
  1122. WEBDAVPROP *lpsDavProp = &sDavProp->sProp;
  1123. object_ptr<IMAPITable> lpHichyTable, lpDelHichyTable;
  1124. object_ptr<IMAPIFolder> lpWasteBox;
  1125. memory_ptr<SPropValue> lpSpropWbEID, lpsPropSingleFld;
  1126. memory_ptr<SPropTagArray> lpPropTagArr;
  1127. size_t cbsize = 0;
  1128. ULONG ulPropTagFldId = 0;
  1129. ULONG ulObjType = 0;
  1130. ULONG ulCmp = 0;
  1131. ULONG ulDelEntries = 0;
  1132. WEBDAVRESPONSE sDavResponse;
  1133. std::string strReqUrl;
  1134. int i;
  1135. // @todo, check input url not to have 3rd level path? .. see input/output list above.
  1136. if(!(m_ulUrlFlag & REQ_PUBLIC))
  1137. strReqUrl = "/caldav/" + urlEncode(m_wstrFldOwner, "utf-8") + "/";
  1138. else
  1139. strReqUrl = "/caldav/public/";
  1140. // all folder properties to fill request.
  1141. cbsize = lpsDavProp->lstProps.size() + 2;
  1142. hr = MAPIAllocateBuffer(CbNewSPropTagArray(cbsize), &~lpPropTagArr);
  1143. if(hr != hrSuccess)
  1144. {
  1145. ec_log_err("Cannot allocate memory");
  1146. return hr;
  1147. }
  1148. ulPropTagFldId = CHANGE_PROP_TYPE(m_lpNamedProps->aulPropTag[PROP_FLDID], PT_UNICODE);
  1149. //add PR_ENTRYID & FolderID in setcolumns along with requested data.
  1150. lpPropTagArr->cValues = (ULONG)cbsize;
  1151. lpPropTagArr->aulPropTag[0] = PR_ENTRYID;
  1152. lpPropTagArr->aulPropTag[1] = ulPropTagFldId;
  1153. i = 2;
  1154. for (const auto &iter : lpsDavProp->lstProps)
  1155. lpPropTagArr->aulPropTag[i++] = GetPropIDForXMLProp(m_lpUsrFld, iter.sPropName, m_converter);
  1156. if (m_ulFolderFlag & SINGLE_FOLDER)
  1157. {
  1158. hr = m_lpUsrFld->GetProps(lpPropTagArr, 0, reinterpret_cast<ULONG *>(&cbsize), &~lpsPropSingleFld);
  1159. if (FAILED(hr)) {
  1160. ec_log_debug("CalDAV::HrListCalendar GetProps failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1161. return hr;
  1162. }
  1163. hr = HrMapValtoStruct(m_lpUsrFld, lpsPropSingleFld, cbsize, NULL, 0, true, &lpsDavProp->lstProps, &sDavResponse);
  1164. if (hr != hrSuccess) {
  1165. ec_log_debug("CalDAV::HrListCalendar HrMapValtoStruct failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1166. return hr;
  1167. }
  1168. lpsMulStatus->lstResp.push_back(sDavResponse);
  1169. return hr;
  1170. }
  1171. hr = HrGetSubCalendars(m_lpSession, m_lpIPMSubtree, nullptr, &~lpHichyTable);
  1172. if (hr != hrSuccess) {
  1173. ec_log_err("Error retrieving subcalendars for IPM_Subtree, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1174. return hr;
  1175. }
  1176. // public definitly doesn't have a wastebasket to filter
  1177. if ((m_ulUrlFlag & REQ_PUBLIC) == 0)
  1178. {
  1179. // always try to get the wastebasket from the current store to filter calendars from
  1180. // make it optional, because we may not have rights on the folder
  1181. hr = HrGetOneProp(m_lpActiveStore, PR_IPM_WASTEBASKET_ENTRYID, &~lpSpropWbEID);
  1182. if(hr != hrSuccess)
  1183. {
  1184. ec_log_debug("CalDAV::HrListCalendar HrGetOneProp(PR_IPM_WASTEBASKET_ENTRYID) failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1185. hr = hrSuccess;
  1186. goto nowaste;
  1187. }
  1188. hr = m_lpActiveStore->OpenEntry(lpSpropWbEID->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpSpropWbEID->Value.bin.lpb), nullptr, MAPI_BEST_ACCESS, &ulObjType, &~lpWasteBox);
  1189. if(hr != hrSuccess)
  1190. {
  1191. hr = hrSuccess;
  1192. goto nowaste;
  1193. }
  1194. hr = HrGetSubCalendars(m_lpSession, lpWasteBox, nullptr, &~lpDelHichyTable);
  1195. if(hr != hrSuccess)
  1196. {
  1197. hr = hrSuccess;
  1198. goto nowaste;
  1199. }
  1200. }
  1201. nowaste:
  1202. hr = lpHichyTable->SetColumns(lpPropTagArr, 0);
  1203. if (hr != hrSuccess) {
  1204. ec_log_debug("CalDAV::HrListCalendar SetColumns failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1205. return hr;
  1206. }
  1207. if (lpDelHichyTable) {
  1208. hr = lpDelHichyTable->SetColumns(lpPropTagArr, 0);
  1209. if (hr != hrSuccess) {
  1210. ec_log_debug("CalDAV::HrListCalendar SetColumns(2) failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1211. return hr;
  1212. }
  1213. }
  1214. while(1)
  1215. {
  1216. rowset_ptr lpRowsALL, lpRowsDeleted;
  1217. hr = lpHichyTable->QueryRows(50, 0, &~lpRowsALL);
  1218. if(hr != hrSuccess || lpRowsALL->cRows == 0)
  1219. break;
  1220. if (lpDelHichyTable)
  1221. hr = lpDelHichyTable->QueryRows(50, 0, &~lpRowsDeleted);
  1222. if(hr != hrSuccess)
  1223. break;
  1224. for (ULONG i = 0; i < lpRowsALL->cRows; ++i) {
  1225. std::wstring wstrFldPath;
  1226. if (lpDelHichyTable && lpRowsDeleted->cRows != 0 && ulDelEntries != lpRowsDeleted->cRows)
  1227. {
  1228. // @todo is this optimized, or just pure luck that this works? don't we need a loop?
  1229. ulCmp = memcmp(lpRowsALL->aRow[i].lpProps[0].Value.bin.lpb,
  1230. lpRowsDeleted->aRow[ulDelEntries].lpProps[0].Value.bin.lpb,
  1231. lpRowsALL->aRow[i].lpProps[0].Value.bin.cb);
  1232. if(ulCmp == 0)
  1233. {
  1234. ++ulDelEntries;
  1235. continue;
  1236. }
  1237. }
  1238. HrSetDavPropName(&(sDavResponse.sPropName), "response", lpsDavProp->sPropName.strNS);
  1239. if (lpRowsALL->aRow[i].lpProps[1].ulPropTag == ulPropTagFldId)
  1240. wstrFldPath = lpRowsALL->aRow[i].lpProps[1].Value.lpszW;
  1241. else if (lpRowsALL->aRow[i].lpProps[0].ulPropTag == PR_ENTRYID)
  1242. // creates new ulPropTagFldId on this folder, or return PR_ENTRYID in wstrFldPath
  1243. // @todo boolean should become default return proptag if save fails, PT_NULL for no default
  1244. hr = HrAddProperty(m_lpActiveStore, lpRowsALL->aRow[i].lpProps[0].Value.bin, ulPropTagFldId, true, &wstrFldPath);
  1245. if (hr != hrSuccess || wstrFldPath.empty()) {
  1246. ec_log_err("Error adding Folder id property, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1247. continue;
  1248. }
  1249. // @todo FOLDER_PREFIX only needed for ulPropTagFldId versions
  1250. // but it doesn't seem we return named folders here? why not?
  1251. wstrFldPath = FOLDER_PREFIX + wstrFldPath + L"/";
  1252. HrSetDavPropName(&(sDavResponse.sHRef.sPropName), "href", lpsDavProp->sPropName.strNS);
  1253. sDavResponse.sHRef.strValue = strReqUrl + W2U(wstrFldPath);
  1254. HrMapValtoStruct(m_lpUsrFld, lpRowsALL->aRow[i].lpProps, lpRowsALL->aRow[i].cValues, NULL, 0, true, &lpsDavProp->lstProps, &sDavResponse);
  1255. lpsMulStatus->lstResp.push_back(sDavResponse);
  1256. sDavResponse.lstsPropStat.clear();
  1257. }
  1258. }
  1259. return hr;
  1260. }
  1261. /**
  1262. * Sets the values on a MAPI object (folder) for PROPPATCH request
  1263. *
  1264. * @note although the proppatch command should return a 207
  1265. * multistatus, setting each prop their own success or error code, the
  1266. * Mac iCal App doesn't parse that, and the user will be left clueless
  1267. * on the update. When we return a direct http error code, iCal App
  1268. * alerts the user (rename default calendar, or rename to an already
  1269. * existing calendar folder.
  1270. *
  1271. * @param[in] lpsDavProp WEBDAVPROP structure containing properties to be modified and its values
  1272. *
  1273. * @return HRESULT
  1274. * @retval MAPI_E_NO_ACCESS Error returned while renaming the default calendar
  1275. *
  1276. * @todo handle all properties in proppatch, and handle unknown properties using named props, which should be returned as patched.
  1277. */
  1278. HRESULT CalDAV::HrHandlePropPatch(WEBDAVPROP *lpsDavProp, WEBDAVMULTISTATUS *lpsMultiStatus)
  1279. {
  1280. HRESULT hr;
  1281. std::wstring wstrConvProp;
  1282. SPropValue sProp;
  1283. WEBDAVRESPONSE sDavResponse;
  1284. WEBDAVPROPSTAT sPropStatusOK;
  1285. WEBDAVPROPSTAT sPropStatusForbidden;
  1286. WEBDAVPROPSTAT sPropStatusCollision;
  1287. HrSetDavPropName(&lpsMultiStatus->sPropName, "multistatus", WEBDAVNS);
  1288. HrSetDavPropName(&sDavResponse.sPropName, "response", WEBDAVNS);
  1289. HrSetDavPropName(&sDavResponse.sHRef.sPropName, "href", WEBDAVNS);
  1290. m_lpRequest->HrGetRequestUrl(&sDavResponse.sHRef.strValue);
  1291. HrSetDavPropName(&sPropStatusOK.sPropName, "propstat", WEBDAVNS);
  1292. HrSetDavPropName(&sPropStatusOK.sStatus.sPropName, "status", WEBDAVNS);
  1293. sPropStatusOK.sStatus.strValue = "HTTP/1.1 200 OK";
  1294. HrSetDavPropName(&sPropStatusOK.sProp.sPropName, "prop", WEBDAVNS);
  1295. HrSetDavPropName(&sPropStatusForbidden.sPropName, "propstat", WEBDAVNS);
  1296. HrSetDavPropName(&sPropStatusForbidden.sStatus.sPropName, "status", WEBDAVNS);
  1297. sPropStatusForbidden.sStatus.strValue = "HTTP/1.1 403 Forbidden";
  1298. HrSetDavPropName(&sPropStatusForbidden.sProp.sPropName, "prop", WEBDAVNS);
  1299. HrSetDavPropName(&sPropStatusCollision.sPropName, "propstat", WEBDAVNS);
  1300. HrSetDavPropName(&sPropStatusCollision.sStatus.sPropName, "status", WEBDAVNS);
  1301. sPropStatusCollision.sStatus.strValue = "HTTP/1.1 409 Conflict";
  1302. HrSetDavPropName(&sPropStatusCollision.sProp.sPropName, "prop", WEBDAVNS);
  1303. for (const auto &iter : lpsDavProp->lstProps) {
  1304. WEBDAVPROPERTY sDavProp;
  1305. sDavProp.sPropName = iter.sPropName; // only copy the propname + namespace part, value is empty
  1306. sProp.ulPropTag = PR_NULL;
  1307. if (iter.sPropName.strPropname == "displayname") {
  1308. // deny rename of default Calendar
  1309. if (!m_blFolderAccess) {
  1310. sPropStatusForbidden.sProp.lstProps.push_back(std::move(sDavProp));
  1311. continue;
  1312. }
  1313. } else if (iter.sPropName.strPropname == "calendar-free-busy-set") {
  1314. // not allowed to select which calendars give freebusy information
  1315. sPropStatusForbidden.sProp.lstProps.push_back(std::move(sDavProp));
  1316. continue;
  1317. } else if (iter.sPropName.strNS.compare(WEBDAVNS) == 0) {
  1318. // only DAV:displayname may be modified, the rest is read-only
  1319. sPropStatusForbidden.sProp.lstProps.push_back(std::move(sDavProp));
  1320. continue;
  1321. }
  1322. sProp.ulPropTag = GetPropIDForXMLProp(m_lpUsrFld, iter.sPropName, m_converter, MAPI_CREATE);
  1323. if (sProp.ulPropTag == PR_NULL) {
  1324. sPropStatusForbidden.sProp.lstProps.push_back(std::move(sDavProp));
  1325. continue;
  1326. }
  1327. if (PROP_TYPE(sProp.ulPropTag) == PT_UNICODE) {
  1328. wstrConvProp = U2W(iter.strValue);
  1329. sProp.Value.lpszW = (WCHAR*)wstrConvProp.c_str();
  1330. } else {
  1331. sProp.Value.bin.cb = iter.strValue.size();
  1332. sProp.Value.bin.lpb = reinterpret_cast<BYTE *>(const_cast<char *>(iter.strValue.data()));
  1333. }
  1334. hr = m_lpUsrFld->SetProps(1, &sProp, NULL);
  1335. if (hr == hrSuccess) {
  1336. sPropStatusOK.sProp.lstProps.push_back(std::move(sDavProp));
  1337. continue;
  1338. }
  1339. if (hr == MAPI_E_COLLISION) {
  1340. // set error 409 collision
  1341. sPropStatusCollision.sProp.lstProps.push_back(std::move(sDavProp));
  1342. // returned on folder rename, directly return an error and skip all other properties, see note above
  1343. return hr;
  1344. }
  1345. // set error 403 forbidden
  1346. sPropStatusForbidden.sProp.lstProps.push_back(std::move(sDavProp));
  1347. }
  1348. // @todo, maybe only do this for certain Mac iCal app versions?
  1349. if (!sPropStatusForbidden.sProp.lstProps.empty())
  1350. return MAPI_E_CALL_FAILED;
  1351. else if (!sPropStatusCollision.sProp.lstProps.empty())
  1352. return MAPI_E_COLLISION;
  1353. // this is the normal code path to return the correct 207 Multistatus
  1354. if (!sPropStatusOK.sProp.lstProps.empty())
  1355. sDavResponse.lstsPropStat.push_back(std::move(sPropStatusOK));
  1356. if (!sPropStatusForbidden.sProp.lstProps.empty())
  1357. sDavResponse.lstsPropStat.push_back(std::move(sPropStatusForbidden));
  1358. if (!sPropStatusCollision.sProp.lstProps.empty())
  1359. sDavResponse.lstsPropStat.push_back(std::move(sPropStatusCollision));
  1360. lpsMultiStatus->lstResp.push_back(std::move(sDavResponse));
  1361. return hrSuccess;
  1362. }
  1363. /**
  1364. * Processes the POST request from caldav client
  1365. *
  1366. * POST is used to request freebusy info of attendees or send
  1367. * meeting request to attendees(used by mac ical.app only)
  1368. *
  1369. * @return HRESULT
  1370. */
  1371. HRESULT CalDAV::HrHandlePost()
  1372. {
  1373. HRESULT hr = hrSuccess;
  1374. std::string strIcal;
  1375. std::unique_ptr<ICalToMapi> lpIcalToMapi;
  1376. hr = m_lpRequest->HrGetBody(&strIcal);
  1377. if (hr != hrSuccess) {
  1378. ec_log_debug("CalDAV::HrHandlePost HrGetBody failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1379. return hr;
  1380. }
  1381. CreateICalToMapi(m_lpDefStore, m_lpAddrBook, false, &unique_tie(lpIcalToMapi));
  1382. if (!lpIcalToMapi)
  1383. {
  1384. ec_log_debug("CalDAV::HrHandlePost CreateICalToMapi failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1385. return MAPI_E_NOT_ENOUGH_MEMORY;
  1386. }
  1387. hr = lpIcalToMapi->ParseICal(strIcal, m_strCharset, m_strSrvTz, m_lpLoginUser, 0);
  1388. if (hr != hrSuccess) {
  1389. ec_log_err("Unable to parse received ical message: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1390. return hr;
  1391. }
  1392. if (lpIcalToMapi->GetFreeBusyInfo(NULL, NULL, NULL, NULL) == hrSuccess)
  1393. return HrHandleFreebusy(lpIcalToMapi.get());
  1394. return HrHandleMeeting(lpIcalToMapi.get());
  1395. }
  1396. /**
  1397. * Handles the caldav clients's request to view freebusy information
  1398. * of attendees
  1399. *
  1400. * @param[in] lpIcalToMapi The ical to mapi conversion object
  1401. * @return HRESULT
  1402. */
  1403. HRESULT CalDAV::HrHandleFreebusy(ICalToMapi *lpIcalToMapi)
  1404. {
  1405. HRESULT hr = hrSuccess;
  1406. object_ptr<ECFreeBusySupport> lpecFBSupport;
  1407. object_ptr<IFreeBusySupport> lpFBSupport;
  1408. std::unique_ptr<MapiToICal> lpMapiToIcal;
  1409. time_t tStart = 0;
  1410. time_t tEnd = 0;
  1411. std::list<std::string> *lstUsers = NULL;
  1412. std::string strUID;
  1413. WEBDAVFBINFO sWebFbInfo;
  1414. SPropValuePtr ptrEmail;
  1415. hr = lpIcalToMapi->GetFreeBusyInfo(&tStart, &tEnd, &strUID, &lstUsers);
  1416. if (hr != hrSuccess) {
  1417. ec_log_debug("CalDAV::HrHandleFreebusy GetFreeBusyInfo failed 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1418. return hr;
  1419. }
  1420. CreateMapiToICal(m_lpAddrBook, "utf-8", &unique_tie(lpMapiToIcal));
  1421. if (lpMapiToIcal == nullptr)
  1422. return MAPI_E_NOT_ENOUGH_MEMORY;
  1423. hr = ECFreeBusySupport::Create(&~lpecFBSupport);
  1424. if (hr != hrSuccess) {
  1425. ec_log_debug("CalDAV::HrHandleFreebusy ECFreeBusySupport::Create failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1426. return hr;
  1427. }
  1428. hr = lpecFBSupport->QueryInterface(IID_IFreeBusySupport, &~lpFBSupport);
  1429. if (hr != hrSuccess) {
  1430. ec_log_debug("CalDAV::HrHandleFreebusy QueryInterface(IID_IFreeBusySupport) failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1431. return hr;
  1432. }
  1433. hr = lpecFBSupport->Open(m_lpSession, m_lpDefStore, true);
  1434. if (hr != hrSuccess) {
  1435. ec_log_debug("CalDAV::HrHandleFreebusy open session failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1436. return hr;
  1437. }
  1438. hr = HrGetOneProp(m_lpActiveUser, PR_SMTP_ADDRESS_A, &~ptrEmail);
  1439. if (hr != hrSuccess) {
  1440. ec_log_debug("CalDAV::HrHandleFreebusy get prop smtp address a failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1441. return hr;
  1442. }
  1443. sWebFbInfo.strOrganiser = ptrEmail->Value.lpszA;
  1444. sWebFbInfo.tStart = tStart;
  1445. sWebFbInfo.tEnd = tEnd;
  1446. sWebFbInfo.strUID = strUID;
  1447. hr = HrGetFreebusy(lpMapiToIcal.get(), lpFBSupport, m_lpAddrBook, lstUsers, &sWebFbInfo);
  1448. if (hr != hrSuccess) {
  1449. // @todo, print which users?
  1450. ec_log_err("Unable to get freebusy information for %zu users: 0x%08X", lstUsers->size(), hr);
  1451. return hr;
  1452. }
  1453. hr = WebDav::HrPostFreeBusy(&sWebFbInfo);
  1454. if (hr != hrSuccess)
  1455. ec_log_debug("CalDAV::HrHandleFreebusy WebDav::HrPostFreeBusy failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1456. return hr;
  1457. }
  1458. /**
  1459. * Handles Mac ical.app clients request to send a meeting request
  1460. * to the attendee.
  1461. *
  1462. * @param[in] lpIcalToMapi ical to mapi conversion object
  1463. * @return HRESULT
  1464. */
  1465. HRESULT CalDAV::HrHandleMeeting(ICalToMapi *lpIcalToMapi)
  1466. {
  1467. HRESULT hr = hrSuccess;
  1468. memory_ptr<SPropValue> lpsGetPropVal;
  1469. object_ptr<IMAPIFolder> lpOutbox;
  1470. object_ptr<IMessage> lpNewMsg;
  1471. SPropValue lpsSetPropVals[2] = {{0}};
  1472. ULONG cValues = 0;
  1473. ULONG ulObjType = 0;
  1474. time_t tModTime = 0;
  1475. SBinary sbEid = {0};
  1476. eIcalType etype = VEVENT;
  1477. static constexpr const SizedSPropTagArray(2, sPropTagArr) =
  1478. {2, {PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID}};
  1479. hr = lpIcalToMapi->GetItemInfo( 0, &etype, &tModTime, &sbEid);
  1480. if ( hr != hrSuccess || etype != VEVENT)
  1481. {
  1482. hr = hrSuccess; // skip VFREEBUSY
  1483. goto exit;
  1484. }
  1485. hr = m_lpDefStore->GetProps(sPropTagArr, 0, &cValues, &~lpsGetPropVal);
  1486. if (hr != hrSuccess && cValues != 2) {
  1487. ec_log_debug("CalDAV::HrHandleMeeting GetProps failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1488. goto exit;
  1489. }
  1490. hr = m_lpDefStore->OpenEntry(lpsGetPropVal[0].Value.bin.cb, reinterpret_cast<ENTRYID *>(lpsGetPropVal[0].Value.bin.lpb), nullptr, MAPI_BEST_ACCESS, &ulObjType, &~lpOutbox);
  1491. if (hr != hrSuccess) {
  1492. ec_log_debug("CalDAV::HrHandleMeeting OpenEntry failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1493. goto exit;
  1494. }
  1495. hr = lpOutbox->CreateMessage(nullptr, MAPI_BEST_ACCESS, &~lpNewMsg);
  1496. if (hr != hrSuccess) {
  1497. ec_log_debug("CalDAV::HrHandleMeeting CreateMessage failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1498. goto exit;
  1499. }
  1500. hr = lpIcalToMapi->GetItem(0, IC2M_NO_ORGANIZER, lpNewMsg);
  1501. if (hr != hrSuccess) {
  1502. ec_log_debug("CalDAV::HrHandleMeeting GetItem failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1503. goto exit;
  1504. }
  1505. lpsSetPropVals[0].ulPropTag = PR_SENTMAIL_ENTRYID;
  1506. lpsSetPropVals[0].Value.bin = lpsGetPropVal[1].Value.bin;
  1507. lpsSetPropVals[1].ulPropTag = PR_DELETE_AFTER_SUBMIT;
  1508. lpsSetPropVals[1].Value.b = false;
  1509. hr = lpNewMsg->SetProps(2, lpsSetPropVals, NULL);
  1510. if (hr != hrSuccess) {
  1511. ec_log_debug("CalDAV::HrHandleMeeting SetProps failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1512. goto exit;
  1513. }
  1514. hr = lpNewMsg->SaveChanges(KEEP_OPEN_READWRITE);
  1515. if (hr != hrSuccess) {
  1516. ec_log_debug("CalDAV::HrHandleMeeting SaveChanges failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1517. goto exit;
  1518. }
  1519. hr = lpNewMsg->SubmitMessage(0);
  1520. if (hr != hrSuccess) {
  1521. ec_log_err("Unable to submit message: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1522. goto exit;
  1523. }
  1524. exit:
  1525. if(hr == hrSuccess)
  1526. m_lpRequest->HrResponseHeader(200, "Ok");
  1527. else
  1528. m_lpRequest->HrResponseHeader(400, "Bad Request");
  1529. return hr;
  1530. }
  1531. /**
  1532. * Converts the mapi message specified by EntryID to ical string.
  1533. *
  1534. * @param[in] lpEid EntryID of the mapi msg to be converted
  1535. * @param[in] lpMtIcal mapi to ical conversion object
  1536. * @param[in] ulFlags Flags used for mapi to ical conversion
  1537. * @param[out] strIcal ical string output
  1538. * @return HRESULT
  1539. */
  1540. HRESULT CalDAV::HrConvertToIcal(const SPropValue *lpEid, MapiToICal *lpMtIcal,
  1541. ULONG ulFlags, std::string *lpstrIcal)
  1542. {
  1543. HRESULT hr = hrSuccess;
  1544. object_ptr<IMessage> lpMessage;
  1545. ULONG ulObjType = 0;
  1546. hr = m_lpActiveStore->OpenEntry(lpEid->Value.bin.cb, reinterpret_cast<ENTRYID *>(lpEid->Value.bin.lpb), nullptr, MAPI_BEST_ACCESS, &ulObjType, &~lpMessage);
  1547. if (hr != hrSuccess && ulObjType == MAPI_MESSAGE)
  1548. {
  1549. ec_log_err("Error opening calendar entry, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1550. return hr;
  1551. }
  1552. hr = lpMtIcal->AddMessage(lpMessage, m_strSrvTz, ulFlags);
  1553. if (hr != hrSuccess)
  1554. {
  1555. ec_log_err("Error converting mapi message to ical, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1556. return hr;
  1557. }
  1558. hr = lpMtIcal->Finalize(0, NULL, lpstrIcal);
  1559. if (hr != hrSuccess)
  1560. {
  1561. ec_log_err("Error creating ical data, error code: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1562. return hr;
  1563. }
  1564. lpMtIcal->ResetObject();
  1565. return hrSuccess;
  1566. }
  1567. /**
  1568. * Set Values for properties requested by caldav client
  1569. *
  1570. * @param[in] lpObj IMAPIProp object, same as lpProps comes from
  1571. * @param[in] lpProps SpropValue array containing values of requested properties
  1572. * @param[in] ulPropCount Count of propety values present in lpProps
  1573. * @param[in] lpMtIcal mapi to ical conversion object pointer
  1574. * @param[in] ulFlags Flags used during mapi to ical conversion (currently only censor flag)
  1575. * @param[in] bPropsFirst first lpProps parameter, then defaults if true
  1576. * @param[in] lstDavProps Pointer to structure containing properties requested by client
  1577. * @param[out] lpsResponse Pointer to Response structure
  1578. * @return HRESULT
  1579. * @retval hrSuccess Always returns hrSuccess
  1580. */
  1581. // @todo cleanup this code, and fix url values
  1582. HRESULT CalDAV::HrMapValtoStruct(LPMAPIPROP lpObj, LPSPropValue lpProps, ULONG ulPropCount, MapiToICal *lpMtIcal, ULONG ulFlags, bool bPropsFirst, std::list<WEBDAVPROPERTY> *lstDavProps, WEBDAVRESPONSE *lpsResponse)
  1583. {
  1584. HRESULT hr;
  1585. WEBDAVPROPERTY sWebProperty;
  1586. std::string strIcal;
  1587. std::string strOwnerURL;
  1588. std::string strCurrentUserURL;
  1589. std::string strPrincipalURL;
  1590. std::string strCalHome;
  1591. WEBDAVPROP sWebProp;
  1592. WEBDAVPROP sWebPropNotFound;
  1593. WEBDAVPROPSTAT sPropStat;
  1594. ULONG ulFolderType;
  1595. SPropValuePtr ptrEmail;
  1596. SPropValuePtr ptrFullname;
  1597. auto lpFoundProp = PCpropFindProp(lpProps, ulPropCount, PR_CONTAINER_CLASS_A);
  1598. if (lpFoundProp && !strncmp (lpFoundProp->Value.lpszA, "IPF.Appointment", strlen("IPF.Appointment")))
  1599. ulFolderType = CALENDAR_FOLDER;
  1600. else if (lpFoundProp && !strncmp (lpFoundProp->Value.lpszA, "IPF.Task", strlen("IPF.Task")))
  1601. ulFolderType = TASKS_FOLDER;
  1602. else
  1603. ulFolderType = OTHER_FOLDER;
  1604. /* ignore errors - nullptr will be handled */
  1605. HrGetOneProp(m_lpActiveUser, PR_SMTP_ADDRESS_A, &~ptrEmail);
  1606. HrGetOneProp(m_lpActiveUser, PR_DISPLAY_NAME_W, &~ptrFullname);
  1607. // owner is DAV namespace, the owner of the resource (url)
  1608. strOwnerURL = "/caldav/" + urlEncode(m_wstrFldOwner, "utf-8") + "/";
  1609. strCurrentUserURL = "/caldav/" + urlEncode(m_wstrUser, "utf-8") + "/";
  1610. // principal always /caldav/m_wstrFldOwner/, except public: full url
  1611. if (m_ulUrlFlag & REQ_PUBLIC) {
  1612. m_lpRequest->HrGetRequestUrl(&strPrincipalURL);
  1613. strCalHome = strPrincipalURL;
  1614. } else {
  1615. strPrincipalURL = "/caldav/" + urlEncode(m_wstrFldOwner, "utf-8") + "/";
  1616. // @todo, displayname of default calendar if empty() ? but see todo in usage below also.
  1617. if (!m_wstrFldName.empty()) {
  1618. strCalHome = strPrincipalURL + urlEncode(m_wstrFldName, "utf-8") + "/";
  1619. } else {
  1620. lpFoundProp = PCpropFindProp(lpProps, ulPropCount, PR_DISPLAY_NAME_W);
  1621. if (lpFoundProp)
  1622. strCalHome = strPrincipalURL + urlEncode(lpFoundProp->Value.lpszW, "utf-8") + "/";
  1623. }
  1624. }
  1625. HrSetDavPropName(&(sWebProp.sPropName), "prop", WEBDAVNS);
  1626. HrSetDavPropName(&(sWebPropNotFound.sPropName), "prop", WEBDAVNS);
  1627. for (const auto &iterprop : *lstDavProps) {
  1628. WEBDAVVALUE sWebVal;
  1629. sWebProperty.lstItems.clear();
  1630. sWebProperty.lstValues.clear();
  1631. sWebProperty = iterprop;
  1632. const std::string &strProperty = sWebProperty.sPropName.strPropname;
  1633. lpFoundProp = PCpropFindProp(lpProps, ulPropCount, GetPropIDForXMLProp(lpObj, sWebProperty.sPropName, m_converter));
  1634. if (strProperty == "resourcetype") {
  1635. // do not set resourcetype for REPORT request(ical data)
  1636. if(!lpMtIcal){
  1637. HrSetDavPropName(&(sWebVal.sPropName), "collection", WEBDAVNS);
  1638. sWebProperty.lstValues.push_back(sWebVal);
  1639. }
  1640. if (lpFoundProp && (!strcmp(lpFoundProp->Value.lpszA ,"IPF.Appointment") || !strcmp(lpFoundProp->Value.lpszA , "IPF.Task"))) {
  1641. HrSetDavPropName(&(sWebVal.sPropName), "calendar", CALDAVNS);
  1642. sWebProperty.lstValues.push_back(sWebVal);
  1643. } else if (m_wstrFldName == L"Inbox") {
  1644. HrSetDavPropName(&(sWebVal.sPropName), "schedule-inbox", CALDAVNS);
  1645. sWebProperty.lstValues.push_back(sWebVal);
  1646. } else if (m_wstrFldName == L"Outbox") {
  1647. HrSetDavPropName(&(sWebVal.sPropName), "schedule-outbox", CALDAVNS);
  1648. sWebProperty.lstValues.push_back(sWebVal);
  1649. }
  1650. } else if (strProperty == "displayname" && (!bPropsFirst || lpFoundProp)) {
  1651. // foldername from given properties (propfind command) username from properties (propsearch command) or fullname of user ("root" props)
  1652. if (bPropsFirst)
  1653. sWebProperty.strValue = SPropValToString(lpFoundProp);
  1654. else if (ptrFullname != nullptr)
  1655. sWebProperty.strValue = W2U(ptrFullname->Value.lpszW);
  1656. } else if (strProperty == "calendar-user-address-set" && (m_ulUrlFlag & REQ_PUBLIC) == 0 && !!ptrEmail) {
  1657. // rfc draft only: http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-11
  1658. HrSetDavPropName(&(sWebVal.sPropName), "href", WEBDAVNS);
  1659. sWebVal.strValue = string("mailto:") + ptrEmail->Value.lpszA;
  1660. sWebProperty.lstValues.push_back(sWebVal);
  1661. } else if (strProperty == "acl" || strProperty == "current-user-privilege-set") {
  1662. HrBuildACL(&sWebProperty);
  1663. } else if (strProperty == "supported-report-set") {
  1664. HrBuildReportSet(&sWebProperty);
  1665. } else if (lpFoundProp &&
  1666. (strProperty == "calendar-description" ||
  1667. strProperty == "last-name" ||
  1668. strProperty == "first-name")
  1669. ) {
  1670. sWebProperty.strValue = SPropValToString(lpFoundProp);
  1671. } else if (strProperty == "getctag" || strProperty == "getetag") {
  1672. // ctag and etag should always be present
  1673. if (lpFoundProp)
  1674. sWebProperty.strValue = SPropValToString(lpFoundProp);
  1675. else
  1676. // this happens when a client (evolution) queries the getctag (local commit time max) on the IPM Subtree
  1677. // (incorrectly configured client)
  1678. sWebProperty.strValue = "0";
  1679. } else if (strProperty == "email-address-set" && (!!ptrEmail || lpFoundProp)) {
  1680. // email from properties (propsearch command) or fullname of user ("root" props)
  1681. HrSetDavPropName(&(sWebVal.sPropName), "email-address", WEBDAVNS);
  1682. sWebVal.strValue = lpFoundProp ? SPropValToString(lpFoundProp) : ptrEmail->Value.lpszA;
  1683. sWebProperty.lstValues.push_back(sWebVal);
  1684. } else if (strProperty == "schedule-inbox-URL" && (m_ulUrlFlag & REQ_PUBLIC) == 0) {
  1685. HrSetDavPropName(&(sWebVal.sPropName), "href", WEBDAVNS);
  1686. sWebVal.strValue = strCurrentUserURL + "Inbox/";
  1687. sWebProperty.lstValues.push_back(sWebVal);
  1688. } else if (strProperty == "schedule-outbox-URL" && (m_ulUrlFlag & REQ_PUBLIC) == 0) {
  1689. HrSetDavPropName(&(sWebVal.sPropName), "href", WEBDAVNS);
  1690. sWebVal.strValue = strCurrentUserURL + "Outbox/";
  1691. sWebProperty.lstValues.push_back(sWebVal);
  1692. } else if (strProperty == "supported-calendar-component-set") {
  1693. if (ulFolderType == CALENDAR_FOLDER) {
  1694. HrSetDavPropName(&(sWebVal.sPropName), "comp","name", "VEVENT", CALDAVNS);
  1695. sWebProperty.lstValues.push_back(sWebVal);
  1696. // actually even only for the standard calendar folder
  1697. HrSetDavPropName(&(sWebVal.sPropName), "comp","name", "VFREEBUSY", CALDAVNS);
  1698. sWebProperty.lstValues.push_back(sWebVal);
  1699. }
  1700. else if (ulFolderType == TASKS_FOLDER) {
  1701. HrSetDavPropName(&(sWebVal.sPropName), "comp","name", "VTODO", CALDAVNS);
  1702. sWebProperty.lstValues.push_back(sWebVal);
  1703. }
  1704. HrSetDavPropName(&(sWebVal.sPropName), "comp","name", "VTIMEZONE", CALDAVNS);
  1705. sWebProperty.lstValues.push_back(sWebVal);
  1706. } else if (lpFoundProp && lpMtIcal && strProperty == "calendar-data") {
  1707. hr = HrConvertToIcal(lpFoundProp, lpMtIcal, ulFlags, &strIcal);
  1708. sWebProperty.strValue = strIcal;
  1709. if (hr != hrSuccess || sWebProperty.strValue.empty()){
  1710. // ical data is empty so discard this calendar entry
  1711. HrSetDavPropName(&(lpsResponse->sStatus.sPropName), "status", WEBDAVNS);
  1712. lpsResponse->sStatus.strValue = "HTTP/1.1 404 Not Found";
  1713. return hr;
  1714. }
  1715. strIcal.clear();
  1716. } else if (strProperty == "calendar-order") {
  1717. if (ulFolderType == CALENDAR_FOLDER) {
  1718. lpFoundProp = PCpropFindProp(lpProps, ulPropCount, PR_ENTRYID);
  1719. if (lpFoundProp)
  1720. HrGetCalendarOrder(lpFoundProp->Value.bin, &sWebProperty.strValue);
  1721. } else {
  1722. // @todo leave not found for messages?
  1723. // set value to 2 for tasks and non default calendar
  1724. // so that ical.app shows default calendar in the list first everytime
  1725. // if this value is left empty, ical.app tries to reset the order
  1726. sWebProperty.strValue = "2";
  1727. }
  1728. } else if (strProperty == "getcontenttype") {
  1729. sWebProperty.strValue = "text/calendar";
  1730. } else if (strProperty == "principal-collection-set") {
  1731. // DAV:
  1732. // This protected property of a resource contains a set of
  1733. // URLs that identify the root collections that contain
  1734. // the principals that are available on the server that
  1735. // implements this resource.
  1736. sWebProperty.strValue = "/caldav/";
  1737. } else if (strProperty == "current-user-principal") {
  1738. // webdav rfc5397
  1739. // We should return the currently authenticated user principal url, but due to sharing, Mac iCal 10.8 gets confused
  1740. // and will use this url as the actual store being accessed, therefor disabling sharing through a different url.
  1741. // So we return the current accessed user principal url to continue in the correct store.
  1742. HrSetDavPropName(&(sWebVal.sPropName), "href", WEBDAVNS);
  1743. sWebVal.strValue = strPrincipalURL;
  1744. sWebProperty.lstValues.push_back(sWebVal);
  1745. } else if (strProperty == "owner") {
  1746. HrSetDavPropName(&(sWebVal.sPropName), "href", WEBDAVNS);
  1747. // always self
  1748. sWebVal.strValue = strOwnerURL;
  1749. sWebProperty.lstValues.push_back(sWebVal);
  1750. } else if (strProperty == "principal-URL") {
  1751. HrSetDavPropName(&(sWebVal.sPropName), "href", WEBDAVNS);
  1752. // self or delegate
  1753. sWebVal.strValue = strPrincipalURL;
  1754. sWebProperty.lstValues.push_back(sWebVal);
  1755. } else if (strProperty == "calendar-home-set" && !strCalHome.empty()) {
  1756. // do not set on public, so thunderbird/lightning doesn't require calendar-user-address-set, schedule-inbox-URL and schedule-outbox-URL
  1757. // public doesn't do meeting requests
  1758. // check here, because lpFoundProp is set to display name and isn't binary
  1759. if ((m_ulUrlFlag & REQ_PUBLIC) == 0 || strAgent.find("Lightning") == string::npos) {
  1760. // Purpose: Identifies the URL of any WebDAV collections that contain
  1761. // calendar collections owned by the associated principal resource.
  1762. // apple seems to use this as the root container where you have your calendars (and would create more)
  1763. // MKCALENDAR would be called with this url as a base.
  1764. HrSetDavPropName(&(sWebVal.sPropName), "href", WEBDAVNS);
  1765. sWebVal.strValue = strPrincipalURL;
  1766. sWebProperty.lstValues.push_back(sWebVal);
  1767. }
  1768. } else if (strProperty == "calendar-user-type") {
  1769. if (SPropValToString(lpFoundProp) == "0")
  1770. sWebProperty.strValue = "INDIVIDUAL";
  1771. } else if (strProperty == "record-type"){
  1772. sWebProperty.strValue = "users";
  1773. } else if (lpFoundProp && lpFoundProp->ulPropTag != PR_NULL) {
  1774. sWebProperty.strValue.assign((char*)lpFoundProp->Value.bin.lpb, lpFoundProp->Value.bin.cb);
  1775. } else {
  1776. sWebPropNotFound.lstProps.push_back(sWebProperty);
  1777. continue;
  1778. }
  1779. sWebProp.lstProps.push_back(sWebProperty);
  1780. }
  1781. HrSetDavPropName(&(sPropStat.sPropName), "propstat", WEBDAVNS);
  1782. HrSetDavPropName(&(sPropStat.sStatus.sPropName), "status", WEBDAVNS);
  1783. if( !sWebProp.lstProps.empty()) {
  1784. sPropStat.sStatus.strValue = "HTTP/1.1 200 OK";
  1785. sPropStat.sProp = sWebProp;
  1786. lpsResponse->lstsPropStat.push_back (sPropStat);
  1787. }
  1788. if( !sWebPropNotFound.lstProps.empty()) {
  1789. sPropStat.sStatus.strValue = "HTTP/1.1 404 Not Found";
  1790. sPropStat.sProp = sWebPropNotFound;
  1791. lpsResponse->lstsPropStat.push_back (sPropStat);
  1792. }
  1793. return hrSuccess;
  1794. }
  1795. /**
  1796. * Sets the Calendar Order to 1 for default calendar folder
  1797. *
  1798. * The function checks if the folder is the default calendar folder, if true sets
  1799. * the calendar-order to 1 or else is set to 2
  1800. *
  1801. * The calendar-order property is set to show the default calendar first in
  1802. * the calendar list in ical.app(Mac). This makes the default kopano calendar default
  1803. * in ical.app too.
  1804. *
  1805. * if the value is left empty ical.app tries to reset the order and sometimes sets
  1806. * a tasks folder as default calendar
  1807. *
  1808. * @param[in] sbEid Entryid of the Folder to be checked
  1809. * @param[out] wstrCalendarOrder string output in which the calendar order is set
  1810. * @return mapi error codes
  1811. * @retval MAPI_E_CALL_FAILED the calendar-order is not set for this folder
  1812. */
  1813. HRESULT CalDAV::HrGetCalendarOrder(SBinary sbEid, std::string *lpstrCalendarOrder)
  1814. {
  1815. HRESULT hr = hrSuccess;
  1816. object_ptr<IMAPIFolder> lpRootCont;
  1817. memory_ptr<SPropValue> lpProp;
  1818. ULONG ulObjType = 0;
  1819. ULONG ulResult = 0;
  1820. lpstrCalendarOrder->assign("2");
  1821. hr = m_lpActiveStore->OpenEntry(0, nullptr, nullptr, 0, &ulObjType, &~lpRootCont);
  1822. if (hr != hrSuccess || ulObjType != MAPI_FOLDER) {
  1823. ec_log_err("Error opening root Container of user %ls, error code: (0x%08X)", m_wstrUser.c_str(), hr);
  1824. return hr;
  1825. }
  1826. // get default calendar folder entry id from root container
  1827. hr = HrGetOneProp(lpRootCont, PR_IPM_APPOINTMENT_ENTRYID, &~lpProp);
  1828. if (hr != hrSuccess) {
  1829. ec_log_debug("CalDAV::HrGetCalendarOrder getprop PR_IPM_APPOINTMENT_ENTRYID failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1830. return hr;
  1831. }
  1832. hr = m_lpActiveStore->CompareEntryIDs(sbEid.cb, (LPENTRYID)sbEid.lpb, lpProp->Value.bin.cb, (LPENTRYID)lpProp->Value.bin.lpb, 0, &ulResult);
  1833. if (hr == hrSuccess && ulResult == TRUE)
  1834. lpstrCalendarOrder->assign("1");
  1835. return hr;
  1836. }
  1837. /**
  1838. * Handles the MOVE http request
  1839. *
  1840. * The request moves mapi message from one folder to another folder
  1841. * The url request refers to the current location of the message and
  1842. * the destination tag in http header specifies the destination folder.
  1843. * The message guid value is same in both url and destination tag.
  1844. *
  1845. * @return mapi error codes
  1846. *
  1847. * @retval MAPI_E_DECLINE_COPY The mapi message is not moved as etag values does not match
  1848. * @retval MAPI_E_NOT_FOUND The mapi message refered by guid is not found
  1849. * @retval MAPI_E_NO_ACCESS The user does not sufficient rights on the mapi message
  1850. *
  1851. */
  1852. HRESULT CalDAV::HrMove()
  1853. {
  1854. HRESULT hr = hrSuccess;
  1855. object_ptr<IMAPIFolder> lpDestFolder;
  1856. std::string strDestination;
  1857. std::string strDestFolder;
  1858. std::string strGuid;
  1859. hr = m_lpRequest->HrGetDestination(&strDestination);
  1860. if (hr != hrSuccess) {
  1861. ec_log_debug("CalDAV::HrMove HrGetDestination failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1862. goto exit;
  1863. }
  1864. hr = HrParseURL(strDestination, NULL, NULL, &strDestFolder);
  1865. if (hr != hrSuccess)
  1866. goto exit;
  1867. hr = HrFindFolder(m_lpActiveStore, m_lpIPMSubtree, m_lpNamedProps, U2W(strDestFolder), &~lpDestFolder);
  1868. if (hr != hrSuccess) {
  1869. ec_log_debug("CalDAV::HrMove HrFindFolder failed: 0x%x %s", hr, GetMAPIErrorMessage(hr));
  1870. goto exit;
  1871. }
  1872. strGuid = StripGuid(strDestination);
  1873. hr = HrMoveEntry(strGuid, lpDestFolder);
  1874. exit:
  1875. // @todo - set e-tag value for the new saved message, so ical.app does not send the GET request
  1876. if (hr == hrSuccess)
  1877. m_lpRequest->HrResponseHeader(200, "OK");
  1878. else if (hr == MAPI_E_DECLINE_COPY)
  1879. m_lpRequest->HrResponseHeader(412, "Precondition Failed"); // entry is modifid on server (sunbird & TB)
  1880. else if( hr == MAPI_E_NOT_FOUND)
  1881. m_lpRequest->HrResponseHeader(404, "Not Found");
  1882. else if(hr == MAPI_E_NO_ACCESS)
  1883. m_lpRequest->HrResponseHeader(403, "Forbidden");
  1884. else
  1885. m_lpRequest->HrResponseHeader(400, "Bad Request");
  1886. return hr;
  1887. }