1 /* http_caldav.c -- Routines for handling CalDAV collections in httpd
2 *
3 * Copyright (c) 1994-2011 Carnegie Mellon University. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The name "Carnegie Mellon University" must not be used to
18 * endorse or promote products derived from this software without
19 * prior written permission. For permission or any legal
20 * details, please contact
21 * Carnegie Mellon University
22 * Center for Technology Transfer and Enterprise Creation
23 * 4615 Forbes Avenue
24 * Suite 302
25 * Pittsburgh, PA 15213
26 * (412) 268-7393, fax: (412) 268-7395
27 * innovation@andrew.cmu.edu
28 *
29 * 4. Redistributions of any form whatsoever must retain the following
30 * acknowledgment:
31 * "This product includes software developed by Computing Services
32 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33 *
34 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41 *
42 */
43 /*
44 * TODO:
45 *
46 * - Make proxying more robust. Currently depends on calendar collections
47 * residing on same server as user's INBOX. Doesn't handle global/shared
48 * calendars.
49 * - Support COPY/MOVE on collections
50 * - Add more required properties?
51 * - calendar-query REPORT (handle timezone, timezone-id)
52 * - free-busy-query REPORT (check ACL and transp on all calendars)
53 * - sync-collection REPORT - need to handle Depth infinity?
54 */
55
56 #include <config.h>
57
58 #include <syslog.h>
59
60 #include <libical/ical.h>
61 #include <libxml/tree.h>
62 #include <libxml/uri.h>
63 #include <sys/types.h>
64
65 #include "acl.h"
66 #include "append.h"
67 #include "caldav_db.h"
68 #include "charset.h"
69 #include "exitcodes.h"
70 #include "global.h"
71 #include "hash.h"
72 #include "httpd.h"
73 #include "http_caldav.h"
74 #include "http_caldav_sched.h"
75 #include "http_dav.h"
76 #include "http_proxy.h"
77 #include "index.h"
78 #include "ical_support.h"
79 #include "jcal.h"
80 #include "xcal.h"
81 #include "map.h"
82 #include "mailbox.h"
83 #include "mboxlist.h"
84 #include "message.h"
85 #include "message_guid.h"
86 #include "proxy.h"
87 #include "times.h"
88 #include "spool.h"
89 #include "strhash.h"
90 #include "stristr.h"
91 #include "tok.h"
92 #include "util.h"
93 #include "version.h"
94 #include "webdav_db.h"
95 #include "xmalloc.h"
96 #include "xml_support.h"
97 #include "xstrlcat.h"
98 #include "xstrlcpy.h"
99 #include "xstrnchr.h"
100 #include "zoneinfo_db.h"
101
102 /* generated headers are not necessarily in current directory */
103 #include "imap/http_err.h"
104 #include "imap/imap_err.h"
105
106 #define TZ_STRIP (1<<9)
107
108
109 #ifdef HAVE_RSCALE
110 #include <unicode/uversion.h>
111
rscale_cmp(const void * a,const void * b)112 static int rscale_cmp(const void *a, const void *b)
113 {
114 /* Convert to uppercase since that's what we prefer to output */
115 return strcmp(ucase(*((char **) a)), ucase(*((char **) b)));
116 }
117 #endif /* HAVE_RSCALE */
118
119
120 static struct caldav_db *auth_caldavdb = NULL;
121 static time_t compile_time;
122 static struct buf ical_prodid_buf = BUF_INITIALIZER;
123
124 unsigned config_allowsched = IMAP_ENUM_CALDAV_ALLOWSCHEDULING_OFF;
125 const char *ical_prodid = NULL;
126 icaltimezone *utc_zone = NULL;
127 struct strlist *cua_domains = NULL;
128 icalarray *rscale_calendars = NULL;
129
130 struct partial_comp_t {
131 icalcomponent_kind kind;
132 arrayu64_t props;
133 struct partial_comp_t *sibling;
134 struct partial_comp_t *child;
135 };
136
137 static struct partial_caldata_t {
138 unsigned expand : 1;
139 struct icalperiodtype range;
140 struct partial_comp_t *comp;
141 } partial_caldata;
142
143 static int meth_options_cal(struct transaction_t *txn, void *params);
144 static int meth_get_head_cal(struct transaction_t *txn, void *params);
145 static int meth_get_head_fb(struct transaction_t *txn, void *params);
146
147 static void my_caldav_init(struct buf *serverinfo);
148 static void my_caldav_auth(const char *userid);
149 static void my_caldav_reset(void);
150 static void my_caldav_shutdown(void);
151
152 static int caldav_parse_path(const char *path,
153 struct request_target_t *tgt, const char **errstr);
154
155 static int caldav_check_precond(struct transaction_t *txn,
156 struct meth_params *params,
157 struct mailbox *mailbox, const void *data,
158 const char *etag, time_t lastmod);
159
160 static int caldav_acl(struct transaction_t *txn, xmlNodePtr priv, int *rights);
161 static int caldav_copy(struct transaction_t *txn, void *obj,
162 struct mailbox *dest_mbox, const char *dest_rsrc,
163 void *destdb, unsigned flags);
164 static int caldav_delete_cal(struct transaction_t *txn,
165 struct mailbox *mailbox,
166 struct index_record *record, void *data);
167 static int caldav_get(struct transaction_t *txn, struct mailbox *mailbox,
168 struct index_record *record, void *data, void **obj);
169 static int caldav_post(struct transaction_t *txn);
170 static int caldav_patch(struct transaction_t *txn, void *obj);
171 static int caldav_put(struct transaction_t *txn, void *obj,
172 struct mailbox *mailbox, const char *resource,
173 void *destdb, unsigned flags);
174 static int caldav_import(struct transaction_t *txn, void *obj,
175 struct mailbox *mailbox, void *destdb,
176 xmlNodePtr root, xmlNsPtr *ns, unsigned flags);
177
178 static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns,
179 struct propfind_ctx *fctx,
180 xmlNodePtr prop, xmlNodePtr resp,
181 struct propstat propstat[], void *rock);
182 static int propfind_restype(const xmlChar *name, xmlNsPtr ns,
183 struct propfind_ctx *fctx,
184 xmlNodePtr prop, xmlNodePtr resp,
185 struct propstat propstat[], void *rock);
186 static int propfind_scheduser(const xmlChar *name, xmlNsPtr ns,
187 struct propfind_ctx *fctx,
188 xmlNodePtr prop, xmlNodePtr resp,
189 struct propstat propstat[], void *rock);
190 static int propfind_caldata(const xmlChar *name, xmlNsPtr ns,
191 struct propfind_ctx *fctx,
192 xmlNodePtr prop, xmlNodePtr resp,
193 struct propstat propstat[], void *rock);
194 static int propfind_calcompset(const xmlChar *name, xmlNsPtr ns,
195 struct propfind_ctx *fctx,
196 xmlNodePtr prop, xmlNodePtr resp,
197 struct propstat propstat[], void *rock);
198 static int proppatch_calcompset(xmlNodePtr prop, unsigned set,
199 struct proppatch_ctx *pctx,
200 struct propstat propstat[], void *rock);
201 static int propfind_suppcaldata(const xmlChar *name, xmlNsPtr ns,
202 struct propfind_ctx *fctx,
203 xmlNodePtr prop, xmlNodePtr resp,
204 struct propstat propstat[], void *rock);
205 static int propfind_maxsize(const xmlChar *name, xmlNsPtr ns,
206 struct propfind_ctx *fctx,
207 xmlNodePtr prop, xmlNodePtr resp,
208 struct propstat propstat[], void *rock);
209 static int propfind_minmaxdate(const xmlChar *name, xmlNsPtr ns,
210 struct propfind_ctx *fctx,
211 xmlNodePtr prop, xmlNodePtr resp,
212 struct propstat propstat[], void *rock);
213 static int propfind_scheddefault(const xmlChar *name, xmlNsPtr ns,
214 struct propfind_ctx *fctx,
215 xmlNodePtr prop, xmlNodePtr resp,
216 struct propstat propstat[], void *rock);
217 static int propfind_schedtag(const xmlChar *name, xmlNsPtr ns,
218 struct propfind_ctx *fctx,
219 xmlNodePtr prop, xmlNodePtr resp,
220 struct propstat propstat[], void *rock);
221 static int propfind_caltransp(const xmlChar *name, xmlNsPtr ns,
222 struct propfind_ctx *fctx,
223 xmlNodePtr prop, xmlNodePtr resp,
224 struct propstat propstat[], void *rock);
225 static int proppatch_caltransp(xmlNodePtr prop, unsigned set,
226 struct proppatch_ctx *pctx,
227 struct propstat propstat[], void *rock);
228 static int propfind_timezone(const xmlChar *name, xmlNsPtr ns,
229 struct propfind_ctx *fctx,
230 xmlNodePtr prop, xmlNodePtr resp,
231 struct propstat propstat[], void *rock);
232 static int proppatch_timezone(xmlNodePtr prop, unsigned set,
233 struct proppatch_ctx *pctx,
234 struct propstat propstat[], void *rock);
235 static int propfind_availability(const xmlChar *name, xmlNsPtr ns,
236 struct propfind_ctx *fctx,
237 xmlNodePtr prop, xmlNodePtr resp,
238 struct propstat propstat[], void *rock);
239 static int proppatch_availability(xmlNodePtr prop, unsigned set,
240 struct proppatch_ctx *pctx,
241 struct propstat propstat[], void *rock);
242 static int propfind_tzservset(const xmlChar *name, xmlNsPtr ns,
243 struct propfind_ctx *fctx,
244 xmlNodePtr prop, xmlNodePtr resp,
245 struct propstat propstat[], void *rock);
246 static int propfind_tzid(const xmlChar *name, xmlNsPtr ns,
247 struct propfind_ctx *fctx,
248 xmlNodePtr prop, xmlNodePtr resp,
249 struct propstat propstat[], void *rock);
250 static int proppatch_tzid(xmlNodePtr prop, unsigned set,
251 struct proppatch_ctx *pctx,
252 struct propstat propstat[], void *rock);
253 static int propfind_rscaleset(const xmlChar *name, xmlNsPtr ns,
254 struct propfind_ctx *fctx,
255 xmlNodePtr prop, xmlNodePtr resp,
256 struct propstat propstat[], void *rock);
257 static int propfind_sharingmodes(const xmlChar *name, xmlNsPtr ns,
258 struct propfind_ctx *fctx,
259 xmlNodePtr prop, xmlNodePtr resp,
260 struct propstat propstat[], void *rock);
261
262 static void strip_vtimezones(icalcomponent *ical);
263
264 static int report_cal_query(struct transaction_t *txn,
265 struct meth_params *rparams,
266 xmlNodePtr inroot, struct propfind_ctx *fctx);
267 static int report_fb_query(struct transaction_t *txn,
268 struct meth_params *rparams,
269 xmlNodePtr inroot, struct propfind_ctx *fctx);
270
271 static const char *begin_icalendar(struct buf *buf);
272 static void end_icalendar(struct buf *buf);
273
274 #define ICALENDAR_CONTENT_TYPE "text/calendar; charset=utf-8"
275
276 static struct mime_type_t caldav_mime_types[] = {
277 /* First item MUST be the default type and storage format */
278 { ICALENDAR_CONTENT_TYPE, "2.0", "ics",
279 (struct buf* (*)(void *)) &my_icalcomponent_as_ical_string,
280 (void * (*)(const struct buf*)) &ical_string_as_icalcomponent,
281 (void (*)(void *)) &icalcomponent_free, &begin_icalendar, &end_icalendar
282 },
283 { "application/calendar+xml; charset=utf-8", NULL, "xcs",
284 (struct buf* (*)(void *)) &icalcomponent_as_xcal_string,
285 (void * (*)(const struct buf*)) &xcal_string_as_icalcomponent,
286 NULL, &begin_xcal, &end_xcal
287 },
288 { "application/calendar+json; charset=utf-8", NULL, "jcs",
289 (struct buf* (*)(void *)) &icalcomponent_as_jcal_string,
290 (void * (*)(const struct buf*)) &jcal_string_as_icalcomponent,
291 NULL, &begin_jcal, &end_jcal
292 },
293 { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
294 };
295
296 static struct patch_doc_t caldav_patch_docs[] = {
297 #ifdef HAVE_VPATCH
298 { ICALENDAR_CONTENT_TYPE "; component=VPATCH; optinfo=\"PATCH-VERSION:1\"",
299 &caldav_patch },
300 #endif
301 { NULL, &caldav_patch /* silence compiler when !HAVE_VPATCH */}
302 };
303
304 /* Array of supported REPORTs */
305 static const struct report_type_t caldav_reports[] = {
306
307 /* WebDAV Versioning (RFC 3253) REPORTs */
308 { "expand-property", NS_DAV, "multistatus", &report_expand_prop,
309 DACL_READ, 0 },
310
311 /* WebDAV ACL (RFC 3744) REPORTs */
312 { "acl-principal-prop-set", NS_DAV, "multistatus", &report_acl_prin_prop,
313 DACL_ADMIN, REPORT_NEED_MBOX | REPORT_DEPTH_ZERO },
314
315 /* WebDAV Sync (RFC 6578) REPORTs */
316 { "sync-collection", NS_DAV, "multistatus", &report_sync_col,
317 DACL_READ, REPORT_NEED_MBOX | REPORT_NEED_PROPS },
318
319 /* CalDAV (RFC 4791) REPORTs */
320 { "calendar-query", NS_CALDAV, "multistatus", &report_cal_query,
321 DACL_READ, REPORT_NEED_MBOX | REPORT_ALLOW_PROPS },
322 { "calendar-multiget", NS_CALDAV, "multistatus", &report_multiget,
323 DACL_READ, REPORT_NEED_MBOX | REPORT_ALLOW_PROPS },
324 { "free-busy-query", NS_CALDAV, NULL, &report_fb_query,
325 DACL_READFB, REPORT_NEED_MBOX },
326
327 { NULL, 0, NULL, NULL, 0, 0 }
328 };
329
330 /* Array of known "live" properties */
331 static const struct prop_entry caldav_props[] = {
332
333 /* WebDAV (RFC 4918) properties */
334 { "creationdate", NS_DAV,
335 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
336 propfind_creationdate, NULL, NULL },
337 { "displayname", NS_DAV,
338 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
339 propfind_fromdb, proppatch_todb, NULL },
340 { "getcontentlanguage", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
341 propfind_fromhdr, NULL, "Content-Language" },
342 { "getcontentlength", NS_DAV,
343 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
344 propfind_getlength, NULL, NULL },
345 { "getcontenttype", NS_DAV,
346 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
347 propfind_getcontenttype, NULL, "Content-Type" },
348 { "getetag", NS_DAV, PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
349 propfind_getetag, NULL, NULL },
350 { "getlastmodified", NS_DAV,
351 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
352 propfind_getlastmod, NULL, NULL },
353 { "lockdiscovery", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
354 propfind_lockdisc, NULL, NULL },
355 { "resourcetype", NS_DAV,
356 PROP_ALLPROP | PROP_COLLECTION | PROP_RESOURCE,
357 propfind_restype, proppatch_restype, "calendar" },
358 { "supportedlock", NS_DAV, PROP_ALLPROP | PROP_RESOURCE,
359 propfind_suplock, NULL, NULL },
360
361 /* WebDAV Versioning (RFC 3253) properties */
362 { "supported-report-set", NS_DAV, PROP_COLLECTION,
363 propfind_reportset, NULL, (void *) caldav_reports },
364
365 /* WebDAV ACL (RFC 3744) properties */
366 { "owner", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
367 propfind_owner, NULL, NULL },
368 { "group", NS_DAV, 0, NULL, NULL, NULL },
369 { "supported-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
370 propfind_supprivset, NULL, NULL },
371 { "current-user-privilege-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
372 propfind_curprivset, NULL, NULL },
373 { "acl", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
374 propfind_acl, NULL, NULL },
375 { "acl-restrictions", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
376 propfind_aclrestrict, NULL, NULL },
377 { "inherited-acl-set", NS_DAV, 0, NULL, NULL, NULL },
378 { "principal-collection-set", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
379 propfind_princolset, NULL, NULL },
380
381 /* WebDAV Quota (RFC 4331) properties */
382 { "quota-available-bytes", NS_DAV, PROP_COLLECTION,
383 propfind_quota, NULL, NULL },
384 { "quota-used-bytes", NS_DAV, PROP_COLLECTION,
385 propfind_quota, NULL, NULL },
386
387 /* WebDAV Current Principal (RFC 5397) properties */
388 { "current-user-principal", NS_DAV, PROP_COLLECTION | PROP_RESOURCE,
389 propfind_curprin, NULL, NULL },
390
391 /* WebDAV POST (RFC 5995) properties */
392 { "add-member", NS_DAV, PROP_COLLECTION,
393 propfind_addmember, NULL, NULL },
394
395 /* WebDAV Sync (RFC 6578) properties */
396 { "sync-token", NS_DAV, PROP_COLLECTION,
397 propfind_sync_token, NULL, SYNC_TOKEN_URL_SCHEME },
398
399 /* WebDAV Sharing (draft-pot-webdav-resource-sharing) properties */
400 { "share-access", NS_DAV, PROP_COLLECTION,
401 propfind_shareaccess, NULL, NULL },
402 { "invite", NS_DAV, PROP_COLLECTION,
403 propfind_invite, NULL, NULL },
404 { "sharer-resource-uri", NS_DAV, PROP_COLLECTION,
405 propfind_sharedurl, NULL, NULL },
406
407 /* Backwards compatibility with Apple calendar sharing clients */
408 { "invite", NS_CS, PROP_COLLECTION,
409 propfind_invite, NULL, "calendarserver-sharing" },
410 { "allowed-sharing-modes", NS_CS, PROP_COLLECTION,
411 propfind_sharingmodes, NULL, NULL },
412 { "shared-url", NS_CS, PROP_COLLECTION,
413 propfind_sharedurl, NULL, "calendarserver-sharing" },
414
415 /* CalDAV (RFC 4791) properties */
416 { "calendar-data", NS_CALDAV, PROP_RESOURCE | PROP_PRESCREEN | PROP_CLEANUP,
417 propfind_caldata, NULL, &partial_caldata },
418 { "schedule-user-address", NS_CYRUS, PROP_RESOURCE,
419 propfind_scheduser, NULL, NULL },
420 { "calendar-description", NS_CALDAV, PROP_COLLECTION,
421 propfind_fromdb, proppatch_todb, NULL },
422 { "calendar-timezone", NS_CALDAV, PROP_COLLECTION | PROP_PRESCREEN,
423 propfind_timezone, proppatch_timezone, NULL },
424 { "supported-calendar-component-set", NS_CALDAV, PROP_COLLECTION,
425 propfind_calcompset, proppatch_calcompset, NULL },
426 { "supported-calendar-data", NS_CALDAV, PROP_COLLECTION,
427 propfind_suppcaldata, NULL, NULL },
428 { "max-resource-size", NS_CALDAV, PROP_COLLECTION,
429 propfind_maxsize, NULL, NULL },
430 { "min-date-time", NS_CALDAV, PROP_COLLECTION,
431 propfind_minmaxdate, NULL, &caldav_epoch },
432 { "max-date-time", NS_CALDAV, PROP_COLLECTION,
433 propfind_minmaxdate, NULL, &caldav_eternity },
434 { "max-instances", NS_CALDAV, 0, NULL, NULL, NULL },
435 { "max-attendees-per-instance", NS_CALDAV, 0, NULL, NULL, NULL },
436
437 /* CalDAV Scheduling (RFC 6638) properties */
438 { "schedule-tag", NS_CALDAV, PROP_RESOURCE,
439 propfind_schedtag, NULL, NULL },
440 { "schedule-default-calendar-URL", NS_CALDAV, PROP_COLLECTION,
441 propfind_scheddefault, NULL, NULL },
442 { "schedule-calendar-transp", NS_CALDAV, PROP_COLLECTION,
443 propfind_caltransp, proppatch_caltransp, NULL },
444
445 /* Calendar Availability (RFC 7953) properties */
446 { "calendar-availability", NS_CALDAV, PROP_COLLECTION | PROP_PRESCREEN,
447 propfind_availability, proppatch_availability, NULL },
448
449 /* Backwards compatibility with Apple VAVAILABILITY clients */
450 { "calendar-availability", NS_CS, PROP_COLLECTION | PROP_PRESCREEN,
451 propfind_availability, proppatch_availability, NULL },
452
453 /* Time Zones by Reference (RFC 7809) properties */
454 { "timezone-service-set", NS_CALDAV, PROP_COLLECTION,
455 propfind_tzservset, NULL, NULL },
456 { "calendar-timezone-id", NS_CALDAV, PROP_COLLECTION,
457 propfind_tzid, proppatch_tzid, NULL },
458
459 /* RSCALE (RFC 7529) properties */
460 { "supported-rscale-set", NS_CALDAV, PROP_COLLECTION,
461 propfind_rscaleset, NULL, NULL },
462
463 /* CalDAV Extensions (draft-daboo-caldav-extensions) properties */
464 { "supported-calendar-component-sets", NS_CALDAV, PROP_COLLECTION,
465 propfind_calcompset, NULL, NULL },
466
467 /* Apple Calendar Server properties */
468 { "getctag", NS_CS, PROP_ALLPROP | PROP_COLLECTION,
469 propfind_sync_token, NULL, "" },
470
471 /* Apple Mobile Me properties */
472 { "bulk-requests", NS_MECOM, PROP_COLLECTION,
473 propfind_bulkrequests, NULL, NULL },
474
475 /* Apple Push Notifications Service properties */
476 { "push-transports", NS_CS, PROP_COLLECTION,
477 propfind_push_transports, NULL, (void *) MBTYPE_CALENDAR },
478 { "pushkey", NS_CS, PROP_COLLECTION,
479 propfind_pushkey, NULL, NULL },
480
481 { NULL, 0, 0, NULL, NULL, NULL }
482 };
483
484
485 static struct meth_params caldav_params = {
486 caldav_mime_types,
487 &caldav_parse_path,
488 &caldav_check_precond,
489 { (db_open_proc_t) &caldav_open_mailbox,
490 (db_close_proc_t) &caldav_close,
491 (db_proc_t) &caldav_begin,
492 (db_proc_t) &caldav_commit,
493 (db_proc_t) &caldav_abort,
494 (db_lookup_proc_t) &caldav_lookup_resource,
495 (db_foreach_proc_t) &caldav_foreach,
496 (db_write_proc_t) &caldav_write,
497 (db_delete_proc_t) &caldav_delete },
498 &caldav_acl,
499 { CALDAV_UID_CONFLICT, &caldav_copy },
500 &caldav_delete_cal,
501 &caldav_get,
502 { CALDAV_LOCATION_OK, MBTYPE_CALENDAR },
503 caldav_patch_docs,
504 { POST_ADDMEMBER | POST_SHARE | POST_BULK, &caldav_post, &caldav_import },
505 { CALDAV_SUPP_DATA, &caldav_put },
506 { 0, caldav_props }, /* Allow infinite depth */
507 caldav_reports
508 };
509
510
511 /* Namespace for CalDAV collections */
512 struct namespace_t namespace_calendar = {
513 URL_NS_CALENDAR, 0, "/dav/calendars", "/.well-known/caldav",
514 http_allow_noauth_get, /*authschemes*/0,
515 MBTYPE_CALENDAR,
516 (ALLOW_READ | ALLOW_POST | ALLOW_WRITE | ALLOW_DELETE |
517 #ifdef HAVE_VPATCH
518 ALLOW_PATCH |
519 #endif
520 #ifdef HAVE_VAVAILABILITY
521 ALLOW_CAL_AVAIL |
522 #endif
523 ALLOW_DAV | ALLOW_PROPPATCH | ALLOW_MKCOL | ALLOW_ACL | ALLOW_CAL ),
524 &my_caldav_init, &my_caldav_auth, my_caldav_reset, &my_caldav_shutdown,
525 &dav_premethod, /*bearer*/NULL,
526 {
527 { &meth_acl, &caldav_params }, /* ACL */
528 { NULL, NULL }, /* BIND */
529 { &meth_copy_move, &caldav_params }, /* COPY */
530 { &meth_delete, &caldav_params }, /* DELETE */
531 { &meth_get_head_cal, NULL }, /* GET */
532 { &meth_get_head_cal, NULL }, /* HEAD */
533 { &meth_lock, &caldav_params }, /* LOCK */
534 { &meth_mkcol, &caldav_params }, /* MKCALENDAR */
535 { &meth_mkcol, &caldav_params }, /* MKCOL */
536 { &meth_copy_move, &caldav_params }, /* MOVE */
537 { &meth_options_cal, &caldav_parse_path }, /* OPTIONS */
538 { &meth_patch, &caldav_params }, /* PATCH */
539 { &meth_post, &caldav_params }, /* POST */
540 { &meth_propfind, &caldav_params }, /* PROPFIND */
541 { &meth_proppatch, &caldav_params }, /* PROPPATCH */
542 { &meth_put, &caldav_params }, /* PUT */
543 { &meth_report, &caldav_params }, /* REPORT */
544 { &meth_trace, &caldav_parse_path }, /* TRACE */
545 { NULL, NULL }, /* UNBIND */
546 { &meth_unlock, &caldav_params } /* UNLOCK */
547 }
548 };
549
550
551 /* Namespace for Freebusy Read URL */
552 struct namespace_t namespace_freebusy = {
553 URL_NS_FREEBUSY, 0, "/freebusy", NULL,
554 http_allow_noauth_get, /*authschemes*/0,
555 MBTYPE_CALENDAR,
556 ALLOW_READ,
557 NULL, NULL, NULL, NULL, NULL, NULL,
558 {
559 { NULL, NULL }, /* ACL */
560 { NULL, NULL }, /* BIND */
561 { NULL, NULL }, /* COPY */
562 { NULL, NULL }, /* DELETE */
563 { &meth_get_head_fb, NULL }, /* GET */
564 { &meth_get_head_fb, NULL }, /* HEAD */
565 { NULL, NULL }, /* LOCK */
566 { NULL, NULL }, /* MKCALENDAR */
567 { NULL, NULL }, /* MKCOL */
568 { NULL, NULL }, /* MOVE */
569 { &meth_options, &caldav_parse_path }, /* OPTIONS */
570 { NULL, NULL }, /* PATCH */
571 { NULL, NULL }, /* POST */
572 { NULL, NULL }, /* PROPFIND */
573 { NULL, NULL }, /* PROPPATCH */
574 { NULL, NULL }, /* PUT */
575 { NULL, NULL }, /* REPORT */
576 { &meth_trace, &caldav_parse_path }, /* TRACE */
577 { NULL, NULL }, /* UNBIND */
578 { NULL, NULL } /* UNLOCK */
579 }
580 };
581
582
583 static const struct cal_comp_t {
584 const char *name;
585 unsigned long type;
586 } cal_comps[] = {
587 { "VEVENT", CAL_COMP_VEVENT },
588 { "VTODO", CAL_COMP_VTODO },
589 { "VJOURNAL", CAL_COMP_VJOURNAL },
590 { "VFREEBUSY", CAL_COMP_VFREEBUSY },
591 #ifdef HAVE_VAVAILABILITY
592 { "VAVAILABILITY", CAL_COMP_VAVAILABILITY },
593 #endif
594 #ifdef HAVE_VPOLL
595 { "VPOLL", CAL_COMP_VPOLL },
596 #endif
597 // { "VTIMEZONE", CAL_COMP_VTIMEZONE },
598 // { "VALARM", CAL_COMP_VALARM },
599 { NULL, 0 }
600 };
601
602
my_caldav_init(struct buf * serverinfo)603 static void my_caldav_init(struct buf *serverinfo)
604 {
605 const char *domains;
606 char *domain;
607 tok_t tok;
608
609 buf_printf(serverinfo, " SQLite/%s", sqlite3_libversion());
610 buf_printf(serverinfo, " LibiCal/%s", ICAL_VERSION);
611 #ifdef HAVE_RSCALE
612 if ((rscale_calendars = icalrecurrencetype_rscale_supported_calendars())) {
613 icalarray_sort(rscale_calendars, &rscale_cmp);
614
615 buf_printf(serverinfo, " ICU4C/%s", U_ICU_VERSION);
616 }
617 #endif
618 buf_printf(serverinfo, " Jansson/%s", JANSSON_VERSION);
619
620 namespace_calendar.enabled =
621 config_httpmodules & IMAP_ENUM_HTTPMODULES_CALDAV;
622
623 if (!namespace_calendar.enabled) return;
624
625 if (!config_getstring(IMAPOPT_CALENDARPREFIX)) {
626 fatal("Required 'calendarprefix' option is not set", EC_CONFIG);
627 }
628
629 #ifdef HAVE_IANA_PARAMS
630 config_allowsched = config_getenum(IMAPOPT_CALDAV_ALLOWSCHEDULING);
631 if (config_allowsched) {
632 namespace_calendar.allow |= ALLOW_CAL_SCHED;
633
634 /* Always treat unknown parameters as IANA */
635 ical_set_unknown_token_handling_setting(ICAL_ASSUME_IANA_TOKEN);
636 }
637
638 if (config_getswitch(IMAPOPT_CALDAV_ALLOWATTACH))
639 namespace_calendar.allow |= ALLOW_CAL_ATTACH;
640
641 #endif /* HAVE_IANA_PARAMS */
642
643 #ifdef HAVE_TZ_BY_REF
644 if (namespace_tzdist.enabled) {
645 /* Tell libical to use our builtin TZ */
646 /* XXX MUST be done before any use of libical, e.g caldav_init() */
647 char zonedir[MAX_MAILBOX_PATH+1];
648
649 snprintf(zonedir, MAX_MAILBOX_PATH, "%s%s",
650 config_dir, FNAME_ZONEINFODIR);
651 set_zone_directory(zonedir);
652 icaltimezone_set_tzid_prefix("");
653 icaltimezone_set_builtin_tzdata(1);
654
655 namespace_calendar.allow |= ALLOW_CAL_NOTZ;
656 }
657 #endif
658
659 caldav_init();
660 webdav_init();
661
662 namespace_principal.enabled = 1;
663 /* Apple clients check principal resources for these DAV tokens */
664 namespace_principal.allow |= namespace_calendar.allow &
665 (ALLOW_CAL | ALLOW_CAL_AVAIL | ALLOW_CAL_SCHED | ALLOW_CAL_ATTACH);
666
667 namespace_freebusy.enabled =
668 config_httpmodules & IMAP_ENUM_HTTPMODULES_FREEBUSY;
669
670 compile_time = calc_compile_time(__TIME__, __DATE__);
671
672 buf_printf(&ical_prodid_buf,
673 "-//CyrusIMAP.org/Cyrus %s//EN", cyrus_version());
674 ical_prodid = buf_cstring(&ical_prodid_buf);
675
676 /* Create an array of calendar-user-adddress-set domains */
677 domains = config_getstring(IMAPOPT_CALENDAR_USER_ADDRESS_SET);
678 if (!domains) domains = config_defdomain;
679 if (!domains) domains = config_servername;
680
681 tok_init(&tok, domains, " \t", TOK_TRIMLEFT|TOK_TRIMRIGHT);
682 while ((domain = tok_next(&tok))) appendstrlist(&cua_domains, domain);
683 tok_fini(&tok);
684
685 utc_zone = icaltimezone_get_utc_timezone();
686 }
687
_create_mailbox(const char * userid,const char * mailboxname,int type,int useracl,int anyoneacl,const char * displayname)688 static int _create_mailbox(const char *userid, const char *mailboxname,
689 int type, int useracl, int anyoneacl,
690 const char *displayname)
691 {
692 int r = 0;
693 char rights[100];
694 struct mailbox *mailbox = NULL;
695
696 r = mboxlist_lookup(mailboxname, NULL, NULL);
697 if (!r) return 0;
698 if (r != IMAP_MAILBOX_NONEXISTENT) return r;
699
700 /* Create locally */
701 r = mboxlist_createmailbox(mailboxname, type,
702 NULL, 0,
703 userid, httpd_authstate,
704 0, 0, 0, 0, displayname ? &mailbox : NULL);
705 if (!r && displayname) {
706 annotate_state_t *astate = NULL;
707
708 r = mailbox_get_annotate_state(mailbox, 0, &astate);
709 if (!r) {
710 const char *annot = DAV_ANNOT_NS "<" XML_NS_DAV ">displayname";
711 struct buf value = BUF_INITIALIZER;
712
713 buf_init_ro_cstr(&value, displayname);
714 r = annotate_state_writemask(astate, annot, userid, &value);
715 }
716
717 mailbox_close(&mailbox);
718 }
719 if (!r && useracl) {
720 cyrus_acl_masktostr(useracl, rights);
721 r = mboxlist_setacl(&httpd_namespace, mailboxname, userid, rights,
722 1, httpd_userid, httpd_authstate);
723 }
724 if (!r && anyoneacl) {
725 cyrus_acl_masktostr(anyoneacl, rights);
726 r = mboxlist_setacl(&httpd_namespace, mailboxname, "anyone", rights,
727 1, httpd_userid, httpd_authstate);
728 }
729
730 if (r) syslog(LOG_ERR, "IOERROR: failed to create %s (%s)",
731 mailboxname, error_message(r));
732 return r;
733 }
734
caldav_create_defaultcalendars(const char * userid)735 int caldav_create_defaultcalendars(const char *userid)
736 {
737 int r;
738 char *mailboxname;
739 struct buf acl = BUF_INITIALIZER;
740
741 /* calendar-home-set */
742 mailboxname = caldav_mboxname(userid, NULL);
743 r = mboxlist_lookup(mailboxname, NULL, NULL);
744 if (r == IMAP_MAILBOX_NONEXISTENT) {
745 /* Find location of INBOX */
746 char *inboxname = mboxname_user_mbox(userid, NULL);
747 mbentry_t *mbentry = NULL;
748
749 r = http_mlookup(inboxname, &mbentry, NULL);
750 free(inboxname);
751 if (r == IMAP_MAILBOX_NONEXISTENT) r = IMAP_INVALID_USER;
752 if (!r && mbentry->server) {
753 proxy_findserver(mbentry->server, &http_protocol, httpd_userid,
754 &backend_cached, NULL, NULL, httpd_in);
755 mboxlist_entry_free(&mbentry);
756 free(mailboxname);
757 return r;
758 }
759 mboxlist_entry_free(&mbentry);
760
761 if (!r) r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
762 ACL_ALL | DACL_READFB, DACL_READFB, NULL);
763 }
764
765 free(mailboxname);
766 if (r) goto done;
767
768 if (config_getswitch(IMAPOPT_CALDAV_CREATE_DEFAULT)) {
769 /* Default calendar */
770 mailboxname = caldav_mboxname(userid, SCHED_DEFAULT);
771 r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
772 ACL_ALL | DACL_READFB, DACL_READFB, "personal");
773 free(mailboxname);
774 if (r) goto done;
775 }
776
777 if (config_getswitch(IMAPOPT_CALDAV_CREATE_SCHED) &&
778 namespace_calendar.allow & ALLOW_CAL_SCHED) {
779 /* Scheduling Inbox */
780 mailboxname = caldav_mboxname(userid, SCHED_INBOX);
781 r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
782 ACL_ALL | DACL_SCHED, DACL_SCHED, NULL);
783 free(mailboxname);
784 if (r) goto done;
785
786 /* Scheduling Outbox */
787 mailboxname = caldav_mboxname(userid, SCHED_OUTBOX);
788 r = _create_mailbox(userid, mailboxname, MBTYPE_CALENDAR,
789 ACL_ALL | DACL_SCHED, 0, NULL);
790 free(mailboxname);
791 if (r) goto done;
792 }
793
794 if (config_getswitch(IMAPOPT_CALDAV_CREATE_ATTACH) &&
795 namespace_calendar.allow & ALLOW_CAL_ATTACH) {
796 /* Managed Attachment Collection */
797 mailboxname = caldav_mboxname(userid, MANAGED_ATTACH);
798 r = _create_mailbox(userid, mailboxname, MBTYPE_COLLECTION,
799 ACL_ALL, ACL_READ, NULL);
800 free(mailboxname);
801 if (r) goto done;
802 }
803
804 done:
805 buf_free(&acl);
806 return r;
807 }
808
my_caldav_auth(const char * userid)809 static void my_caldav_auth(const char *userid)
810 {
811 int r;
812
813 if (httpd_userisadmin ||
814 global_authisa(httpd_authstate, IMAPOPT_PROXYSERVERS)) {
815 /* admin or proxy from frontend - won't have DAV database */
816 return;
817 }
818 if (config_mupdate_server && !config_getstring(IMAPOPT_PROXYSERVERS)) {
819 /* proxy-only server - won't have DAV database */
820 return;
821 }
822 else {
823 /* Open CalDAV DB for 'userid' */
824 my_caldav_reset();
825 auth_caldavdb = caldav_open_userid(userid);
826 if (!auth_caldavdb) fatal("Unable to open CalDAV DB", EC_IOERR);
827 }
828
829 /* Auto-provision calendars for 'userid' */
830 r = caldav_create_defaultcalendars(userid);
831 if (r) {
832 syslog(LOG_ERR, "could not autoprovision calendars for userid %s: %s",
833 userid, error_message(r));
834 }
835 }
836
my_caldav_reset(void)837 static void my_caldav_reset(void)
838 {
839 if (auth_caldavdb) caldav_close(auth_caldavdb);
840 auth_caldavdb = NULL;
841 }
842
my_caldav_shutdown(void)843 static void my_caldav_shutdown(void)
844 {
845 if (rscale_calendars) icalarray_free(rscale_calendars);
846 rscale_calendars = NULL;
847
848 buf_free(&ical_prodid_buf);
849
850 freestrlist(cua_domains);
851 cua_domains = NULL;
852
853 my_caldav_reset();
854 webdav_done();
855 caldav_done();
856 }
857
858
859 /* Parse request-target path in CalDAV namespace */
caldav_parse_path(const char * path,struct request_target_t * tgt,const char ** errstr)860 static int caldav_parse_path(const char *path,
861 struct request_target_t *tgt, const char **errstr)
862 {
863 int r;
864
865 r = calcarddav_parse_path(path, tgt,
866 config_getstring(IMAPOPT_CALENDARPREFIX),
867 errstr);
868 if (r) return r;
869
870 /* Set proper Allow bits based on collection */
871 if (tgt->namespace && tgt->namespace->id == URL_NS_FREEBUSY) {
872 /* Read-only collections */
873 tgt->allow = ALLOW_READ;
874 }
875 else if (!tgt->collection) {
876 /* Allow POST to cal-home-set (share reply) */
877 tgt->allow |= ALLOW_POST;
878 }
879 else if (!strncmp(tgt->collection, MANAGED_ATTACH, strlen(MANAGED_ATTACH))) {
880 /* Read-only non-calendar collection */
881 tgt->allow = ALLOW_READ;
882
883 tgt->flags = TGT_MANAGED_ATTACH;
884 }
885 else if (!strncmp(tgt->collection, SCHED_INBOX, strlen(SCHED_INBOX))) {
886 /* Can only read and DELETE resources from this collection */
887 tgt->allow &= ALLOW_READ_MASK;
888
889 if (tgt->resource) tgt->allow |= ALLOW_DELETE;
890
891 tgt->flags = TGT_SCHED_INBOX;
892 }
893 else if (!strncmp(tgt->collection, SCHED_OUTBOX, strlen(SCHED_OUTBOX))){
894 /* Can only POST to this collection (free/busy request) */
895 tgt->allow &= ALLOW_READ_MASK;
896
897 if (!tgt->resource) tgt->allow |= ALLOW_POST;
898
899 tgt->flags = TGT_SCHED_OUTBOX;
900 }
901 else if (tgt->resource) {
902 /* Resource in regular calendar collection (POST for managed attach) */
903 tgt->allow |= (namespace_calendar.allow & ALLOW_PATCH) | ALLOW_POST;
904 }
905
906 return 0;
907 }
908
909
910 /* Check headers for any preconditions */
caldav_check_precond(struct transaction_t * txn,struct meth_params * params,struct mailbox * mailbox,const void * data,const char * etag,time_t lastmod)911 static int caldav_check_precond(struct transaction_t *txn,
912 struct meth_params *params,
913 struct mailbox *mailbox, const void *data,
914 const char *etag, time_t lastmod)
915 {
916 const struct caldav_data *cdata = (const struct caldav_data *) data;
917 const char *stag = cdata && cdata->organizer ? cdata->sched_tag : NULL;
918 const char **hdr;
919 int precond;
920
921 /* Do normal WebDAV/HTTP checks (primarily for lock-token via If header) */
922 precond = dav_check_precond(txn, params, mailbox, data, etag, lastmod);
923 if (precond == HTTP_PRECOND_FAILED &&
924 cdata->comp_flags.tzbyref && !cdata->organizer && cdata->sched_tag) {
925 /* Resource has just had VTIMEZONEs stripped -
926 check if conditional matches previous ETag */
927
928 precond = check_precond(txn, cdata->sched_tag, lastmod);
929 }
930 if (!(precond == HTTP_OK || precond == HTTP_PARTIAL)) return precond;
931
932 /* Per RFC 6638, check Schedule-Tag */
933 if ((hdr = spool_getheader(txn->req_hdrs, "If-Schedule-Tag-Match"))) {
934 /* Special case for Apple 'If-Schedule-Tag-Match:' with no value
935 * and also no schedule tag on the record - let that match */
936 if (cdata && !stag && !hdr[0][0]) return precond;
937 if (etagcmp(hdr[0], stag)) return HTTP_PRECOND_FAILED;
938 }
939
940 if (txn->meth == METH_GET || txn->meth == METH_HEAD) {
941 /* Fill in Schedule-Tag for successful GET/HEAD */
942 txn->resp_body.stag = stag;
943 }
944
945 return precond;
946 }
947
948
caldav_acl(struct transaction_t * txn,xmlNodePtr priv,int * rights)949 static int caldav_acl(struct transaction_t *txn, xmlNodePtr priv, int *rights)
950 {
951 if (!xmlStrcmp(priv->ns->href, BAD_CAST XML_NS_CALDAV)) {
952 /* CalDAV privileges */
953 switch (txn->req_tgt.flags) {
954 case TGT_SCHED_INBOX:
955 if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver"))
956 *rights |= DACL_SCHED;
957 else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver-invite"))
958 *rights |= DACL_INVITE;
959 else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-deliver-reply"))
960 *rights |= DACL_REPLY;
961 else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-query-freebusy"))
962 *rights |= DACL_SCHEDFB;
963 else {
964 /* DAV:not-supported-privilege */
965 txn->error.precond = DAV_SUPP_PRIV;
966 }
967 break;
968 case TGT_SCHED_OUTBOX:
969 if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send"))
970 *rights |= DACL_SCHED;
971 else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-invite"))
972 *rights |= DACL_INVITE;
973 else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-reply"))
974 *rights |= DACL_REPLY;
975 else if (!xmlStrcmp(priv->name, BAD_CAST "schedule-send-freebusy"))
976 *rights |= DACL_SCHEDFB;
977 else {
978 /* DAV:not-supported-privilege */
979 txn->error.precond = DAV_SUPP_PRIV;
980 }
981 break;
982 default:
983 if (!xmlStrcmp(priv->name, BAD_CAST "read-free-busy"))
984 *rights |= DACL_READFB;
985 else {
986 /* DAV:not-supported-privilege */
987 txn->error.precond = DAV_SUPP_PRIV;
988 }
989 break;
990 }
991
992 /* Done processing this priv */
993 return 1;
994 }
995 else if (!xmlStrcmp(priv->ns->href, BAD_CAST XML_NS_DAV)) {
996 /* WebDAV privileges */
997 if (!xmlStrcmp(priv->name, BAD_CAST "all")) {
998 switch (txn->req_tgt.flags) {
999 case TGT_SCHED_INBOX:
1000 /* DAV:all aggregates CALDAV:schedule-deliver */
1001 *rights |= DACL_SCHED;
1002 break;
1003 case TGT_SCHED_OUTBOX:
1004 /* DAV:all aggregates CALDAV:schedule-send */
1005 *rights |= DACL_SCHED;
1006 break;
1007 default:
1008 /* DAV:all aggregates CALDAV:read-free-busy */
1009 *rights |= DACL_READFB;
1010 break;
1011 }
1012 }
1013 else if (!xmlStrcmp(priv->name, BAD_CAST "read")) {
1014 switch (txn->req_tgt.flags) {
1015 case TGT_SCHED_INBOX:
1016 case TGT_SCHED_OUTBOX:
1017 break;
1018 default:
1019 /* DAV:read aggregates CALDAV:read-free-busy */
1020 *rights |= DACL_READFB;
1021 break;
1022 }
1023 }
1024 }
1025
1026 /* Process this priv in meth_acl() */
1027 return 0;
1028 }
1029
_scheduling_enabled(struct transaction_t * txn,const struct mailbox * mailbox)1030 static int _scheduling_enabled(struct transaction_t *txn,
1031 const struct mailbox *mailbox)
1032 {
1033 if (!(namespace_calendar.allow & ALLOW_CAL_SCHED)) return 0;
1034
1035 const char *entry = DAV_ANNOT_NS "<" XML_NS_CYRUS ">scheduling-enabled";
1036 struct buf buf = BUF_INITIALIZER;
1037 int is_enabled = 1;
1038
1039 annotatemore_lookupmask(mailbox->name, entry, httpd_userid, &buf);
1040 /* legacy */
1041 if (!strcasecmp(buf_cstring(&buf), "no"))
1042 is_enabled = 0;
1043 if (!strcasecmp(buf_cstring(&buf), "F"))
1044 is_enabled = 0;
1045
1046 const char **hdr = spool_getheader(txn->req_hdrs, "Scheduling-Enabled");
1047 if (hdr && !strcasecmp(hdr[0], "F"))
1048 is_enabled = 0;
1049
1050 buf_free(&buf);
1051 return is_enabled;
1052 }
1053
1054 /* Perform a COPY/MOVE request
1055 *
1056 * preconditions:
1057 * CALDAV:supported-calendar-data
1058 * CALDAV:valid-calendar-data
1059 * CALDAV:valid-calendar-object-resource
1060 * CALDAV:supported-calendar-component
1061 * CALDAV:no-uid-conflict (DAV:href)
1062 * CALDAV:calendar-collection-location-ok
1063 * CALDAV:max-resource-size
1064 * CALDAV:min-date-time
1065 * CALDAV:max-date-time
1066 * CALDAV:max-instances
1067 * CALDAV:max-attendees-per-instance
1068 */
caldav_copy(struct transaction_t * txn,void * obj,struct mailbox * dest_mbox,const char * dest_rsrc,void * destdb,unsigned flags)1069 static int caldav_copy(struct transaction_t *txn, void *obj,
1070 struct mailbox *dest_mbox, const char *dest_rsrc,
1071 void *destdb, unsigned flags)
1072 {
1073 int r;
1074 struct caldav_db *db = (struct caldav_db *)destdb;
1075
1076 icalcomponent *comp, *ical = (icalcomponent *) obj;
1077 const char *organizer = NULL;
1078 icalproperty *prop;
1079
1080 if (!ical) {
1081 txn->error.precond = CALDAV_VALID_DATA;
1082 return HTTP_FORBIDDEN;
1083 }
1084
1085 if (_scheduling_enabled(txn, dest_mbox)) {
1086 comp = icalcomponent_get_first_real_component(ical);
1087 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
1088 if (prop) organizer = icalproperty_get_organizer(prop);
1089 if (organizer) flags |= NEW_STAG;
1090 }
1091
1092 /* Store source resource at destination */
1093 /* XXX - set calendar-user-address based on original message? */
1094 r = caldav_store_resource(txn, ical, dest_mbox, dest_rsrc, db, flags, NULL);
1095
1096 return r;
1097 }
1098
1099
1100 static void decrement_refcount(const char *managed_id,
1101 struct mailbox *attachments,
1102 struct webdav_db *webdavdb);
1103 static struct webdav_data *increment_refcount(const char *managed_id,
1104 struct webdav_db *webdavdb);
1105
1106 enum {
1107 REFCNT_DEC = -1,
1108 REFCNT_HOLD = 0,
1109 REFCNT_INC = 1
1110 };
1111
update_refcount(const char * mid,short * op,struct mailbox * attachments)1112 static void update_refcount(const char *mid, short *op,
1113 struct mailbox *attachments)
1114 {
1115 switch (*op) {
1116 case REFCNT_DEC:
1117 decrement_refcount(mid, attachments, attachments->local_webdav);
1118 break;
1119
1120 case REFCNT_INC:
1121 increment_refcount(mid, attachments->local_webdav);
1122 break;
1123 }
1124 }
1125
1126 /* Check an iCal object to see if managed attachments are being manipulated */
manage_attachments(struct transaction_t * txn,struct mailbox * mailbox,icalcomponent * ical,struct caldav_data * cdata,icalcomponent ** oldical,char ** schedule_address)1127 static int manage_attachments(struct transaction_t *txn,
1128 struct mailbox *mailbox,
1129 icalcomponent *ical, struct caldav_data *cdata,
1130 icalcomponent **oldical, char **schedule_address)
1131 {
1132 /* Compare any managed attachments in new and existing resources */
1133 char *mailboxname = NULL;
1134 struct mailbox *attachments = NULL;
1135 struct webdav_db *webdavdb = NULL;
1136 struct hash_table mattach_table = HASH_TABLE_INITIALIZER;
1137 icalcomponent *comp = NULL;
1138 icalcomponent_kind kind;
1139 icalproperty *prop;
1140 icalparameter *param;
1141 const char *mid;
1142 short *op;
1143 int r, ret = 0;
1144
1145 /* Open attachments collection for writing */
1146 mailboxname = caldav_mboxname(httpd_userid, MANAGED_ATTACH);
1147 r = mailbox_open_iwl(mailboxname, &attachments);
1148 if (r) {
1149 syslog(LOG_ERR, "mailbox_open_iwl(%s) failed: %s",
1150 mailboxname, error_message(r));
1151 ret = HTTP_SERVER_ERROR;
1152 }
1153 else {
1154 /* Open the WebDAV DB corresponding to the attachments collection */
1155 webdavdb = mailbox_open_webdav(attachments);
1156 if (!webdavdb) {
1157 syslog(LOG_ERR, "webdav_open_mailbox(%s) failed",
1158 attachments->name);
1159 ret = HTTP_SERVER_ERROR;
1160 }
1161 }
1162 free(mailboxname);
1163
1164 if (ret) return ret;
1165
1166 /* Create hash table of managed attachments in new resource */
1167 construct_hash_table(&mattach_table, 10, 1);
1168
1169 if (ical) {
1170 comp = icalcomponent_get_first_real_component(ical);
1171 kind = icalcomponent_isa(comp);
1172 }
1173
1174 for (; comp;
1175 comp = icalcomponent_get_next_component(ical, kind)) {
1176
1177 for (prop = icalcomponent_get_first_property(comp,
1178 ICAL_ATTACH_PROPERTY);
1179 prop;
1180 prop = icalcomponent_get_next_property(comp,
1181 ICAL_ATTACH_PROPERTY)) {
1182
1183 icalattach *attach = icalproperty_get_attach(prop);
1184
1185 if (icalattach_get_is_url(attach)) {
1186 struct webdav_data *wdata;
1187
1188 param = icalproperty_get_managedid_parameter(prop);
1189 if (!param) continue;
1190
1191 /* Find DAV record for the attachment with this managed-id */
1192 mid = icalparameter_get_managedid(param);
1193 webdav_lookup_uid(webdavdb, mid, &wdata);
1194
1195 if (!wdata->dav.rowid) {
1196 txn->error.precond = CALDAV_VALID_MANAGEDID;
1197 ret = HTTP_FORBIDDEN;
1198 goto done;
1199 }
1200
1201 if (!hash_lookup(mid, &mattach_table)) {
1202 /* Assume attachment is being added to ical */
1203 op = xmalloc(sizeof(short));
1204 *op = REFCNT_INC;
1205 hash_insert(mid, op, &mattach_table);
1206 }
1207 }
1208 else {
1209 /* XXX Do we want to strip and manage inline attachments? */
1210 }
1211 }
1212 }
1213
1214 /* Compare existing managed attachments to those in new resource */
1215 if (cdata->comp_flags.mattach) {
1216 struct index_record record;
1217
1218 syslog(LOG_NOTICE, "LOADING ICAL %u", cdata->dav.imap_uid);
1219
1220 /* Load message containing the resource and parse iCal data */
1221 r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record);
1222 if (r) {
1223 txn->error.desc = "Failed to read record";
1224 ret = HTTP_SERVER_ERROR;
1225 goto done;
1226 }
1227
1228 *oldical = record_to_ical(mailbox, &record, schedule_address);
1229 comp = icalcomponent_get_first_real_component(*oldical);
1230 kind = icalcomponent_isa(comp);
1231
1232 for (; comp;
1233 comp = icalcomponent_get_next_component(*oldical, kind)) {
1234 for (prop = icalcomponent_get_first_property(comp,
1235 ICAL_ATTACH_PROPERTY);
1236 prop;
1237 prop = icalcomponent_get_next_property(comp,
1238 ICAL_ATTACH_PROPERTY)) {
1239
1240 param = icalproperty_get_managedid_parameter(prop);
1241 if (!param) continue;
1242
1243 mid = icalparameter_get_managedid(param);
1244 op = hash_lookup(mid, &mattach_table);
1245 if (!op) {
1246 /* Attachment removed from ical */
1247 op = xmalloc(sizeof(short));
1248 *op = REFCNT_DEC;
1249 hash_insert(mid, op, &mattach_table);
1250 }
1251 else if (*op != REFCNT_DEC) {
1252 /* Attachment still in ical */
1253 *op = REFCNT_HOLD;
1254 }
1255 }
1256 }
1257 }
1258
1259 /* Update reference counts of attachments in hash table */
1260 hash_enumerate(&mattach_table,
1261 (void(*)(const char*,void*,void*)) &update_refcount,
1262 attachments);
1263
1264 done:
1265 free_hash_table(&mattach_table, free);
1266 mailbox_close(&attachments);
1267
1268 return ret;
1269 }
1270
1271
get_schedule_addresses(struct transaction_t * txn,strarray_t * addresses)1272 static void get_schedule_addresses(struct transaction_t *txn,
1273 strarray_t *addresses)
1274 {
1275 struct buf buf = BUF_INITIALIZER;
1276
1277 /* allow override of schedule-address per-message (FM specific) */
1278 const char **hdr = spool_getheader(txn->req_hdrs, "Schedule-Address");
1279
1280 if (hdr) {
1281 if (!strncasecmp(hdr[0], "mailto:", 7))
1282 strarray_append(addresses, hdr[0]+7);
1283 else
1284 strarray_append(addresses, hdr[0]);
1285 }
1286 else {
1287 /* find schedule address based on the destination calendar's user */
1288
1289 /* check calendar-user-address-set for target user */
1290 const char *annotname =
1291 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-user-address-set";
1292 char *mailboxname = caldav_mboxname(txn->req_tgt.userid, NULL);
1293 int r = annotatemore_lookupmask(mailboxname, annotname,
1294 txn->req_tgt.userid, &buf);
1295 free(mailboxname);
1296 if (!r && buf.len > 7 &&
1297 !strncasecmp(buf_cstring(&buf), "mailto:", 7)) {
1298 strarray_append(addresses, buf_cstring(&buf) + 7);
1299 }
1300 else if (strchr(txn->req_tgt.userid, '@')) {
1301 /* userid corresponding to target */
1302 strarray_append(addresses, txn->req_tgt.userid);
1303 }
1304 else {
1305 /* append fully qualified userids */
1306 struct strlist *domains;
1307
1308 for (domains = cua_domains; domains; domains = domains->next) {
1309 buf_reset(&buf);
1310 buf_printf(&buf, "%s@%s", txn->req_tgt.userid, domains->s);
1311
1312 strarray_appendm(addresses, buf_release(&buf));
1313 }
1314 }
1315 }
1316
1317 buf_free(&buf);
1318 }
1319
1320
1321 /* Perform scheduling actions for a DELETE request */
caldav_delete_cal(struct transaction_t * txn,struct mailbox * mailbox,struct index_record * record,void * data)1322 static int caldav_delete_cal(struct transaction_t *txn,
1323 struct mailbox *mailbox,
1324 struct index_record *record, void *data)
1325 {
1326 struct caldav_data *cdata = (struct caldav_data *) data;
1327 icalcomponent *ical = NULL;
1328 struct buf buf = BUF_INITIALIZER;
1329 char *schedule_address = NULL;
1330 int r = 0;
1331
1332 /* Only process deletes on regular calendar collections */
1333 if (txn->req_tgt.flags) return 0;
1334
1335 if ((namespace_calendar.allow & ALLOW_CAL_ATTACH) &&
1336 cdata->comp_flags.mattach) {
1337 r = manage_attachments(txn, mailbox, NULL,
1338 cdata, &ical, &schedule_address);
1339 if (r) goto done;
1340 }
1341
1342 if (cdata->organizer) {
1343 /* Scheduling object resource */
1344 strarray_t schedule_addresses = STRARRAY_INITIALIZER;
1345 const char **hdr;
1346
1347 /* XXX - check date range? - don't send in the past */
1348
1349 /* Load message containing the resource and parse iCal data */
1350 if (!ical) ical = record_to_ical(mailbox, record, &schedule_address);
1351
1352 if (!ical) {
1353 syslog(LOG_ERR,
1354 "meth_delete: failed to parse iCalendar object %s:%u",
1355 txn->req_tgt.mbentry->name, record->uid);
1356 return HTTP_SERVER_ERROR;
1357 }
1358
1359 if (schedule_address) {
1360 strarray_appendm(&schedule_addresses, schedule_address);
1361 schedule_address = NULL;
1362 }
1363 get_schedule_addresses(txn, &schedule_addresses);
1364
1365 /* XXX - after legacy records are gone, we can strip this and just not send a
1366 * cancellation if deleting a record which was never replied to... */
1367
1368 char *userid = mboxname_to_userid(txn->req_tgt.mbentry->name);
1369 if (strarray_find_case(&schedule_addresses, cdata->organizer, 0) >= 0) {
1370 /* Organizer scheduling object resource */
1371 schedule_address = xstrdupnull(cdata->organizer);
1372 if (_scheduling_enabled(txn, mailbox))
1373 sched_request(userid, schedule_address, ical, NULL);
1374 }
1375 else if (!(hdr = spool_getheader(txn->req_hdrs, "Schedule-Reply")) ||
1376 strcasecmp(hdr[0], "F")) {
1377 /* Attendee scheduling object resource */
1378 schedule_address = xstrdupnull(strarray_nth(&schedule_addresses, 0));
1379 if (_scheduling_enabled(txn, mailbox) && schedule_address)
1380 sched_reply(userid, schedule_address, ical, NULL);
1381 }
1382
1383 free(userid);
1384 strarray_fini(&schedule_addresses);
1385 }
1386
1387 done:
1388 if (ical) icalcomponent_free(ical);
1389 free(schedule_address);
1390 buf_free(&buf);
1391
1392 return r;
1393 }
1394
begin_icalendar(struct buf * buf)1395 static const char *begin_icalendar(struct buf *buf)
1396 {
1397 /* Begin iCalendar stream */
1398 buf_setcstr(buf, "BEGIN:VCALENDAR\r\n");
1399 buf_printf(buf, "PRODID:%s\r\n", ical_prodid);
1400 buf_appendcstr(buf, "VERSION:2.0\r\n");
1401
1402 return "";
1403 }
1404
end_icalendar(struct buf * buf)1405 static void end_icalendar(struct buf *buf)
1406 {
1407 /* End iCalendar stream */
1408 buf_setcstr(buf, "END:VCALENDAR\r\n");
1409 }
1410
export_calendar(struct transaction_t * txn)1411 static int export_calendar(struct transaction_t *txn)
1412 {
1413 int ret = 0, r, precond;
1414 struct resp_body_t *resp_body = &txn->resp_body;
1415 struct buf *buf = &resp_body->payload;
1416 struct mailbox *mailbox = NULL;
1417 static char etag[33];
1418 struct hash_table tzid_table;
1419 static const char *displayname_annot =
1420 DAV_ANNOT_NS "<" XML_NS_DAV ">displayname";
1421 struct buf attrib = BUF_INITIALIZER;
1422 const char **hdr, *sep;
1423 struct mime_type_t *mime = NULL;
1424
1425 /* Check requested MIME type:
1426 1st entry in caldav_mime_types array MUST be default MIME type */
1427 if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
1428 mime = get_accept_type(hdr, caldav_mime_types);
1429 else mime = caldav_mime_types;
1430 if (!mime) return HTTP_NOT_ACCEPTABLE;
1431
1432 /* Open mailbox for reading */
1433 r = mailbox_open_irl(txn->req_tgt.mbentry->name, &mailbox);
1434 if (r) {
1435 syslog(LOG_ERR, "http_mailbox_open(%s) failed: %s",
1436 txn->req_tgt.mbentry->name, error_message(r));
1437 txn->error.desc = error_message(r);
1438 ret = HTTP_SERVER_ERROR;
1439 goto done;
1440 }
1441
1442 /* Check any preconditions */
1443 sprintf(etag, "%u-%u-%u",
1444 mailbox->i.uidvalidity, mailbox->i.last_uid, mailbox->i.exists);
1445 precond = check_precond(txn, etag, mailbox->index_mtime);
1446
1447 switch (precond) {
1448 case HTTP_OK:
1449 case HTTP_NOT_MODIFIED:
1450 /* Fill in ETag, Last-Modified, Expires, and Cache-Control */
1451 txn->resp_body.etag = etag;
1452 txn->resp_body.lastmod = mailbox->index_mtime;
1453 txn->resp_body.maxage = 3600; /* 1 hr */
1454 txn->flags.cc |= CC_MAXAGE | CC_REVALIDATE; /* don't use stale data */
1455
1456 if (precond != HTTP_NOT_MODIFIED) break;
1457
1458 GCC_FALLTHROUGH
1459
1460 default:
1461 /* We failed a precondition - don't perform the request */
1462 ret = precond;
1463 goto done;
1464 }
1465
1466 /* Setup for chunked response */
1467 txn->flags.te |= TE_CHUNKED;
1468 txn->flags.vary |= VARY_ACCEPT;
1469 txn->resp_body.type = mime->content_type;
1470
1471 /* Set filename of resource */
1472 r = annotatemore_lookupmask(mailbox->name, displayname_annot,
1473 httpd_userid, &attrib);
1474 /* fall back to last part of mailbox name */
1475 if (r || !attrib.len) buf_setcstr(&attrib, strrchr(mailbox->name, '.') + 1);
1476
1477 buf_reset(&txn->buf);
1478 buf_printf(&txn->buf, "%s.%s", buf_cstring(&attrib), mime->file_ext);
1479 txn->resp_body.fname = buf_cstring(&txn->buf);
1480
1481 /* Short-circuit for HEAD request */
1482 if (txn->meth == METH_HEAD) {
1483 response_header(HTTP_OK, txn);
1484 return 0;
1485 }
1486
1487 /* iCalendar data in response should not be transformed */
1488 txn->flags.cc |= CC_NOTRANSFORM;
1489
1490 /* Create hash table for TZIDs */
1491 construct_hash_table(&tzid_table, 10, 1);
1492
1493 /* Begin (converted) iCalendar stream */
1494 sep = mime->begin_stream(buf);
1495 write_body(HTTP_OK, txn, buf_cstring(buf), buf_len(buf));
1496
1497 struct mailbox_iter *iter =
1498 mailbox_iter_init(mailbox, 0, ITER_SKIP_EXPUNGED|ITER_SKIP_DELETED);
1499
1500 const message_t *msg;
1501 while ((msg = mailbox_iter_step(iter))) {
1502 const struct index_record *record = msg_record(msg);
1503 icalcomponent *ical;
1504
1505 /* Map and parse existing iCalendar resource */
1506 ical = record_to_ical(mailbox, record, NULL);
1507
1508 if (ical) {
1509 icalcomponent *comp;
1510
1511 for (comp = icalcomponent_get_first_component(ical,
1512 ICAL_ANY_COMPONENT);
1513 comp;
1514 comp = icalcomponent_get_next_component(ical,
1515 ICAL_ANY_COMPONENT)) {
1516 struct buf *cal_str;
1517 icalcomponent_kind kind = icalcomponent_isa(comp);
1518
1519 /* Don't duplicate any TZIDs in our iCalendar */
1520 if (kind == ICAL_VTIMEZONE_COMPONENT) {
1521 icalproperty *prop =
1522 icalcomponent_get_first_property(comp,
1523 ICAL_TZID_PROPERTY);
1524 const char *tzid = icalproperty_get_tzid(prop);
1525
1526 if (hash_lookup(tzid, &tzid_table)) continue;
1527 else hash_insert(tzid, (void *)0xDEADBEEF, &tzid_table);
1528 }
1529
1530 /* Include this component in our iCalendar */
1531 if (r++ && *sep) {
1532 /* Add separator, if necessary */
1533 buf_reset(buf);
1534 buf_printf_markup(buf, 0, sep);
1535 write_body(0, txn, buf_cstring(buf), buf_len(buf));
1536 }
1537 cal_str = mime->from_object(comp);
1538 write_body(0, txn, buf_base(cal_str), buf_len(cal_str));
1539 buf_destroy(cal_str);
1540 }
1541
1542 icalcomponent_free(ical);
1543 }
1544 }
1545
1546 mailbox_iter_done(&iter);
1547
1548 free_hash_table(&tzid_table, NULL);
1549
1550 /* End (converted) iCalendar stream */
1551 mime->end_stream(buf);
1552 write_body(0, txn, buf_cstring(buf), buf_len(buf));
1553
1554 /* End of output */
1555 write_body(0, txn, NULL, 0);
1556
1557 done:
1558 buf_free(&attrib);
1559 mailbox_close(&mailbox);
1560
1561 return ret;
1562 }
1563
1564
1565 /*
1566 * mboxlist_findall() callback function to list calendars
1567 */
1568
1569 struct cal_info {
1570 char shortname[MAX_MAILBOX_NAME];
1571 char displayname[MAX_MAILBOX_NAME];
1572 unsigned flags;
1573 unsigned long types;
1574 };
1575
1576 enum {
1577 CAL_IS_DEFAULT = (1<<0),
1578 CAL_CAN_DELETE = (1<<1),
1579 CAL_CAN_ADMIN = (1<<2),
1580 CAL_IS_PUBLIC = (1<<3),
1581 CAL_IS_TRANSP = (1<<4)
1582 };
1583
1584 struct list_cal_rock {
1585 struct cal_info *cal;
1586 unsigned len;
1587 unsigned alloc;
1588 };
1589
list_cal_cb(const mbentry_t * mbentry,void * rock)1590 static int list_cal_cb(const mbentry_t *mbentry, void *rock)
1591 {
1592 struct list_cal_rock *lrock = (struct list_cal_rock *) rock;
1593 struct cal_info *cal;
1594 static size_t inboxlen = 0;
1595 static size_t outboxlen = 0;
1596 static size_t defaultlen = 0;
1597 char *shortname;
1598 size_t len;
1599 int r, rights, any_rights = 0;
1600 static const char *displayname_annot =
1601 DAV_ANNOT_NS "<" XML_NS_DAV ">displayname";
1602 static const char *schedtransp_annot =
1603 DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
1604 static const char *calcompset_annot =
1605 DAV_ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
1606 struct buf displayname = BUF_INITIALIZER, schedtransp = BUF_INITIALIZER;
1607 struct buf calcompset = BUF_INITIALIZER;
1608
1609 if (!inboxlen) inboxlen = strlen(SCHED_INBOX) - 1;
1610 if (!outboxlen) outboxlen = strlen(SCHED_OUTBOX) - 1;
1611 if (!defaultlen) defaultlen = strlen(SCHED_DEFAULT) - 1;
1612
1613 /* Make sure its a calendar */
1614 if (mbentry->mbtype != MBTYPE_CALENDAR) goto done;
1615
1616 /* Make sure its readable */
1617 rights = httpd_myrights(httpd_authstate, mbentry);
1618 if ((rights & DACL_READ) != DACL_READ) goto done;
1619
1620 /* Don't list scheduling Inbox/Outbox */
1621 shortname = strrchr(mbentry->name, '.') + 1;
1622 len = strlen(shortname);
1623
1624 if ((len == inboxlen && !strncmp(shortname, SCHED_INBOX, inboxlen)) ||
1625 (len == outboxlen && !strncmp(shortname, SCHED_OUTBOX, outboxlen)))
1626 goto done;
1627
1628 /* Lookup DAV:displayname */
1629 r = annotatemore_lookupmask(mbentry->name, displayname_annot,
1630 httpd_userid, &displayname);
1631 /* fall back to the last part of the mailbox name */
1632 if (r || !displayname.len) buf_setcstr(&displayname, shortname);
1633
1634 /* Make sure we have room in our array */
1635 if (lrock->len == lrock->alloc) {
1636 lrock->alloc += 100;
1637 lrock->cal = xrealloc(lrock->cal,
1638 lrock->alloc * sizeof(struct cal_info));
1639 }
1640
1641 /* Add our calendar to the array */
1642 cal = &lrock->cal[lrock->len];
1643 strlcpy(cal->shortname, shortname, MAX_MAILBOX_NAME);
1644 strlcpy(cal->displayname, buf_cstring(&displayname), MAX_MAILBOX_NAME);
1645 cal->flags = 0;
1646
1647 /* Is this the default calendar? */
1648 if (len == defaultlen && !strncmp(shortname, SCHED_DEFAULT, defaultlen)) {
1649 cal->flags |= CAL_IS_DEFAULT;
1650 }
1651
1652 /* Can we delete this calendar? */
1653 else if (rights & DACL_RMCOL) {
1654 cal->flags |= CAL_CAN_DELETE;
1655 }
1656
1657 /* Can we admin this calendar? */
1658 if (rights & DACL_ADMIN) {
1659 cal->flags |= CAL_CAN_ADMIN;
1660 }
1661
1662 /* Is this calendar public? */
1663 if (mbentry->acl) {
1664 struct auth_state *auth_anyone = auth_newstate("anyone");
1665
1666 any_rights = cyrus_acl_myrights(auth_anyone, mbentry->acl);
1667 auth_freestate(auth_anyone);
1668 }
1669 if ((any_rights & DACL_READ) == DACL_READ) {
1670 cal->flags |= CAL_IS_PUBLIC;
1671 }
1672
1673 /* Is this calendar transparent? */
1674 r = annotatemore_lookupmask(mbentry->name, schedtransp_annot,
1675 httpd_userid, &schedtransp);
1676 if (!r && !strcmp(buf_cstring(&schedtransp), "transparent")) {
1677 cal->flags |= CAL_IS_TRANSP;
1678 }
1679 buf_free(&schedtransp);
1680
1681 /* Which component types are supported? */
1682 r = annotatemore_lookupmask(mbentry->name, calcompset_annot,
1683 httpd_userid, &calcompset);
1684 if (!r && buf_len(&calcompset)) {
1685 cal->types = strtoul(buf_cstring(&calcompset), NULL, 10);
1686 }
1687 else {
1688 /* ALL component types */
1689 cal->types = -1;
1690 }
1691 buf_free(&calcompset);
1692
1693 lrock->len++;
1694
1695 done:
1696 buf_free(&displayname);
1697
1698 return 0;
1699 }
1700
cal_compare(const void * a,const void * b)1701 static int cal_compare(const void *a, const void *b)
1702 {
1703 struct cal_info *c1 = (struct cal_info *) a;
1704 struct cal_info *c2 = (struct cal_info *) b;
1705
1706 return strcmp(c1->displayname, c2->displayname);
1707 }
1708
1709
1710 struct list_tzid_rock {
1711 struct buf *body;
1712 unsigned *level;
1713 };
1714
list_tzid_cb(const char * tzid,int tzidlen,struct zoneinfo * zi,void * rock)1715 int list_tzid_cb(const char *tzid,
1716 int tzidlen __attribute__((unused)),
1717 struct zoneinfo *zi __attribute__((unused)),
1718 void *rock)
1719 {
1720 struct list_tzid_rock *tzrock = (struct list_tzid_rock *) rock;
1721
1722 /* Skip Etc and other non-standard zones */
1723 if (strnchr(tzid, '/', tzidlen) && strncmp(tzid, "Etc/", 4)) {
1724 buf_printf_markup(tzrock->body, *tzrock->level,
1725 "<option>%.*s</option>", tzidlen, tzid);
1726 }
1727
1728 return 0;
1729 }
1730
1731
1732 /* Create a HTML document listing all calendars available to the user */
list_calendars(struct transaction_t * txn)1733 static int list_calendars(struct transaction_t *txn)
1734 {
1735 int ret = 0, precond, rights;
1736 char mboxlist[MAX_MAILBOX_PATH+1];
1737 struct stat sbuf;
1738 time_t lastmod;
1739 const char *etag, *base_path = txn->req_tgt.path;
1740 unsigned level = 0, i;
1741 struct buf *body = &txn->resp_body.payload;
1742 struct list_cal_rock lrock;
1743 const char *proto = NULL;
1744 const char *host = NULL;
1745 const struct cal_comp_t *comp;
1746 #include "imap/http_caldav_js.h"
1747
1748 /* stat() mailboxes.db for Last-Modified and ETag */
1749 snprintf(mboxlist, MAX_MAILBOX_PATH, "%s%s", config_dir, FNAME_MBOXLIST);
1750 stat(mboxlist, &sbuf);
1751 lastmod = MAX(compile_time, sbuf.st_mtime);
1752 assert(!buf_len(&txn->buf));
1753 buf_printf(&txn->buf, "%ld-%ld-%ld",
1754 compile_time, sbuf.st_mtime, sbuf.st_size);
1755
1756 /* stat() config file for Last-Modified and ETag */
1757 stat(config_filename, &sbuf);
1758 lastmod = MAX(lastmod, sbuf.st_mtime);
1759 buf_printf(&txn->buf, "-%ld-%ld", sbuf.st_mtime, sbuf.st_size);
1760 etag = buf_cstring(&txn->buf);
1761
1762 /* Check any preconditions */
1763 precond = check_precond(txn, etag, lastmod);
1764
1765 switch (precond) {
1766 case HTTP_OK:
1767 case HTTP_NOT_MODIFIED:
1768 /* Fill in ETag, Last-Modified, and Expires */
1769 txn->resp_body.etag = etag;
1770 txn->resp_body.lastmod = lastmod;
1771 txn->flags.cc |= CC_REVALIDATE;
1772
1773 if (precond != HTTP_NOT_MODIFIED) break;
1774
1775 GCC_FALLTHROUGH
1776
1777 default:
1778 /* We failed a precondition - don't perform the request */
1779 ret = precond;
1780 goto done;
1781 }
1782
1783 /* Setup for chunked response */
1784 txn->flags.te |= TE_CHUNKED;
1785 txn->resp_body.type = "text/html; charset=utf-8";
1786
1787 /* Short-circuit for HEAD request */
1788 if (txn->meth == METH_HEAD) {
1789 response_header(HTTP_OK, txn);
1790 goto done;
1791 }
1792
1793 /* Send HTML header */
1794 buf_reset(body);
1795 buf_printf_markup(body, level, HTML_DOCTYPE);
1796 buf_printf_markup(body, level++, "<html>");
1797 buf_printf_markup(body, level++, "<head>");
1798 buf_printf_markup(body, level, "<title>%s</title>", "Available Calendars");
1799 buf_printf_markup(body, level++, "<script type=\"text/javascript\">");
1800 buf_appendcstr(body, "//<![CDATA[\n");
1801 buf_printf(body, (const char *) http_caldav_js,
1802 cyrus_version(), http_caldav_js_len);
1803 buf_appendcstr(body, "//]]>\n");
1804 buf_printf_markup(body, --level, "</script>");
1805 buf_printf_markup(body, level++, "<noscript>");
1806 buf_printf_markup(body, level, "<i>*** %s ***</i>",
1807 "JavaScript required to create/modify/delete calendars");
1808 buf_printf_markup(body, --level, "</noscript>");
1809 buf_printf_markup(body, --level, "</head>");
1810 buf_printf_markup(body, level++, "<body>");
1811
1812 write_body(HTTP_OK, txn, buf_cstring(body), buf_len(body));
1813 buf_reset(body);
1814
1815 /* Check ACL for current user */
1816 rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
1817
1818 if (rights & DACL_MKCOL) {
1819 /* Add "create" form */
1820 struct list_tzid_rock tzrock = { body, &level };
1821
1822 buf_printf_markup(body, level, "<h2>%s</h2>", "Create New Calendar");
1823 buf_printf_markup(body, level++, "<form name='create'>");
1824 buf_printf_markup(body, level++, "<table cellpadding=5>");
1825 buf_printf_markup(body, level++, "<tr>");
1826 buf_printf_markup(body, level, "<td align=right>Name:</td>");
1827 buf_printf_markup(body, level,
1828 "<td><input name=name size=30 maxlength=40></td>");
1829 buf_printf_markup(body, --level, "</tr>");
1830
1831 buf_printf_markup(body, level++, "<tr>");
1832 buf_printf_markup(body, level, "<td align=right>Description:</td>");
1833 buf_printf_markup(body, level,
1834 "<td><input name=desc size=75 maxlength=120></td>");
1835 buf_printf_markup(body, --level, "</tr>");
1836
1837 buf_printf_markup(body, level++, "<tr>");
1838 buf_printf_markup(body, level, "<td align=right>Components:</td>");
1839 buf_printf_markup(body, level++, "<td>");
1840 for (comp = cal_comps; comp->name; comp++) {
1841 buf_printf_markup(body, level,
1842 "<input type=checkbox%s name=comp value=%s>%s",
1843 !strcmp(comp->name, "VEVENT") ? " checked" : "",
1844 comp->name, comp->name);
1845 }
1846 buf_printf_markup(body, --level, "</td>");
1847 buf_printf_markup(body, --level, "</tr>");
1848
1849 if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
1850 buf_printf_markup(body, level++, "<tr>");
1851 buf_printf_markup(body, level, "<td align=right>Time Zone:</td>");
1852 buf_printf_markup(body, level++, "<td>");
1853 buf_printf_markup(body, level++, "<select name=tzid>");
1854 buf_printf_markup(body, level, "<option></option>");
1855 zoneinfo_find(NULL, 1, 0, &list_tzid_cb, &tzrock);
1856 buf_printf_markup(body, --level, "</select>");
1857 buf_printf_markup(body, --level, "</td>");
1858 buf_printf_markup(body, --level, "</tr>");
1859 }
1860
1861 buf_printf_markup(body, level++, "<tr>");
1862 buf_printf_markup(body, level, "<td></td>");
1863 buf_printf_markup(body, level,
1864 "<td><br><input type=button value='Create'"
1865 " onclick=\"createCalendar('%s')\">"
1866 " <input type=reset></td>",
1867 base_path);
1868 buf_printf_markup(body, --level, "</tr>");
1869
1870 buf_printf_markup(body, --level, "</table>");
1871 buf_printf_markup(body, --level, "</form>");
1872
1873 buf_printf_markup(body, level, "<br><hr><br>");
1874
1875 write_body(0, txn, buf_cstring(body), buf_len(body));
1876 buf_reset(body);
1877 }
1878
1879 buf_printf_markup(body, level, "<h2>%s</h2>", "Available Calendars");
1880 buf_printf_markup(body, level++, "<table border cellpadding=5>");
1881
1882 /* Create base URL for calendars */
1883 http_proto_host(txn->req_hdrs, &proto, &host);
1884 buf_reset(&txn->buf);
1885 buf_printf(&txn->buf, "%s://%s%s", proto, host, txn->req_tgt.path);
1886
1887 memset(&lrock, 0, sizeof(struct list_cal_rock));
1888 mboxlist_mboxtree(txn->req_tgt.mbentry->name,
1889 list_cal_cb, &lrock, MBOXTREE_SKIP_ROOT);
1890
1891 /* Sort calendars by displayname */
1892 qsort(lrock.cal, lrock.len, sizeof(struct cal_info), &cal_compare);
1893
1894 /* Add available calendars with action items */
1895 for (i = 0; i < lrock.len; i++) {
1896 struct cal_info *cal = &lrock.cal[i];
1897
1898 /* Send a body chunk once in a while */
1899 if (buf_len(body) > PROT_BUFSIZE) {
1900 write_body(0, txn, buf_cstring(body), buf_len(body));
1901 buf_reset(body);
1902 }
1903
1904 /* Calendar name */
1905 buf_printf_markup(body, level++, "<tr>");
1906 buf_printf_markup(body, level, "<td>%s%s%s",
1907 (cal->flags & CAL_IS_DEFAULT) ? "<b>" : "",
1908 cal->displayname,
1909 (cal->flags & CAL_IS_DEFAULT) ? "</b>" : "");
1910
1911 /* Supported components list */
1912 buf_printf_markup(body, level++, "<td>");
1913 buf_printf_markup(body, level++,
1914 "<select multiple name=comp size=3"
1915 " onChange=\"compsetCalendar('%s%s', '%s', this.options)\">",
1916 base_path, cal->shortname, cal->displayname);
1917 for (comp = cal_comps; comp->name; comp++) {
1918 buf_printf_markup(body, level, "<option%s>%s</option>",
1919 (cal->types & comp->type) ? " selected" : "",
1920 comp->name);
1921 }
1922 buf_printf_markup(body, --level, "</select>");
1923 buf_printf_markup(body, --level, "</td>");
1924
1925 /* Subscribe link */
1926 buf_printf_markup(body, level,
1927 "<td><a href=\"webcal://%s%s%s\">Subscribe</a></td>",
1928 host, base_path, cal->shortname);
1929
1930 /* Download link */
1931 buf_printf_markup(body, level, "<td><a href=\"%s%s\">Download</a></td>",
1932 base_path, cal->shortname);
1933
1934 /* Delete button */
1935 buf_printf_markup(body, level,
1936 "<td><input type=button%s value='Delete'"
1937 " onclick=\"deleteCalendar('%s%s', '%s')\"></td>",
1938 !(cal->flags & CAL_CAN_DELETE) ? " disabled" : "",
1939 base_path, cal->shortname, cal->displayname);
1940
1941 /* Public (shared) checkbox */
1942 buf_printf_markup(body, level,
1943 "<td><input type=checkbox%s%s name=share"
1944 " onclick=\"shareCalendar('%s%s', this.checked)\">"
1945 "Public</td>",
1946 !(cal->flags & CAL_CAN_ADMIN) ? " disabled" : "",
1947 (cal->flags & CAL_IS_PUBLIC) ? " checked" : "",
1948 base_path, cal->shortname);
1949
1950 /* Transparent checkbox */
1951 buf_printf_markup(body, level,
1952 "<td><input type=checkbox%s%s name=transp"
1953 " onclick=\"transpCalendar('%s%s', this.checked)\">"
1954 "Transparent</td>",
1955 !(cal->flags & CAL_CAN_ADMIN) ? " disabled" : "",
1956 (cal->flags & CAL_IS_TRANSP) ? " checked" : "",
1957 base_path, cal->shortname);
1958
1959 buf_printf_markup(body, --level, "</tr>");
1960 }
1961
1962 free(lrock.cal);
1963
1964 /* Finish list */
1965 buf_printf_markup(body, --level, "</table>");
1966
1967 /* Finish HTML */
1968 buf_printf_markup(body, --level, "</body>");
1969 buf_printf_markup(body, --level, "</html>");
1970 write_body(0, txn, buf_cstring(body), buf_len(body));
1971
1972 /* End of output */
1973 write_body(0, txn, NULL, 0);
1974
1975 done:
1976 return ret;
1977 }
1978
1979
1980 /* Parse an RFC3339 date/time per
1981 http://www.calconnect.org/pubdocs/CD0903%20Freebusy%20Read%20URL.pdf */
icaltime_from_rfc3339_string(const char * str)1982 static struct icaltimetype icaltime_from_rfc3339_string(const char *str)
1983 {
1984 struct icaltimetype tt = icaltime_null_time();
1985 size_t size;
1986
1987 size = strlen(str);
1988
1989 if (size == 20) {
1990 /* UTC */
1991 if (sscanf(str, "%4u-%02u-%02uT%02u:%02u:%02uZ",
1992 &tt.year, &tt.month, &tt.day,
1993 &tt.hour, &tt.minute, &tt.second) < 6) {
1994 goto fail;
1995 }
1996
1997 tt = icaltime_normalize(tt);
1998 }
1999 else if (size == 25) {
2000 /* TZ offset */
2001 int offset_hour, offset_minute;
2002 char offset_sign;
2003
2004 if (sscanf(str, "%4u-%02u-%02uT%02u:%02u:%02u%c%02u:%02u",
2005 &tt.year, &tt.month, &tt.day,
2006 &tt.hour, &tt.minute, &tt.second,
2007 &offset_sign, &offset_hour, &offset_minute) < 9) {
2008 goto fail;
2009 }
2010
2011 if (offset_sign == '-') {
2012 /* negative offset */
2013 offset_hour *= -1;
2014 offset_minute *= -1;
2015 }
2016 else if (offset_sign != '+') {
2017 goto fail;
2018 }
2019
2020 icaltime_adjust(&tt, 0, -offset_hour, -offset_minute, 0);
2021 }
2022 else {
2023 goto fail;
2024 }
2025
2026 icaltime_set_utc(&tt, 1);
2027 return tt;
2028
2029 fail:
2030 return icaltime_null_time();
2031 }
2032
2033
2034 struct timezone_rock {
2035 icalcomponent *old;
2036 icalcomponent *new;
2037 };
2038
add_timezone(icalparameter * param,void * data)2039 static void add_timezone(icalparameter *param, void *data)
2040 {
2041 struct timezone_rock *tzrock = (struct timezone_rock *) data;
2042 const char *tzid = icalparameter_get_tzid(param);
2043
2044 /* Check if this tz is in our new object */
2045 if (!icalcomponent_get_timezone(tzrock->new, tzid)) {
2046 icalcomponent *vtz = NULL;
2047
2048 if (tzrock->old) {
2049 /* Fetch tz from old object and add to new */
2050 icaltimezone *tz = icalcomponent_get_timezone(tzrock->old, tzid);
2051 if (tz) vtz = icalcomponent_new_clone(icaltimezone_get_component(tz));
2052 }
2053 else {
2054 /* Fetch tz from our tzdist repository */
2055 struct buf buf = BUF_INITIALIZER;
2056 const char *path;
2057 int fd;
2058
2059 /* Open and mmap the timezone file */
2060 buf_printf(&buf, "%s%s/%s.ics", config_dir, FNAME_ZONEINFODIR, tzid);
2061 path = buf_cstring(&buf);
2062
2063 if ((fd = open(path, O_RDONLY)) != -1) {
2064 struct buf data = BUF_INITIALIZER;
2065 icalcomponent *ical;
2066
2067 buf_init_mmap(&data, 1, fd, path, MAP_UNKNOWN_LEN, NULL);
2068 ical = ical_string_as_icalcomponent(&data);
2069 vtz = icalcomponent_get_first_component(ical,
2070 ICAL_VTIMEZONE_COMPONENT);
2071 icalcomponent_remove_component(ical, vtz);
2072 icalcomponent_free(ical);
2073 buf_free(&data);
2074 close(fd);
2075 }
2076 buf_free(&buf);
2077 }
2078
2079 if (vtz) icalcomponent_add_component(tzrock->new, vtz);
2080 }
2081 }
2082
2083
2084 /* Perform a GET/HEAD request on a CalDAV resource */
caldav_get(struct transaction_t * txn,struct mailbox * mailbox,struct index_record * record,void * data,void ** obj)2085 static int caldav_get(struct transaction_t *txn, struct mailbox *mailbox,
2086 struct index_record *record, void *data, void **obj)
2087 {
2088 int r, rights;
2089
2090 if (!(txn->req_tgt.collection || txn->req_tgt.userid))
2091 return HTTP_NO_CONTENT;
2092
2093 /* Check ACL for current user */
2094 rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
2095 if ((rights & DACL_READ) != DACL_READ) {
2096 /* DAV:need-privileges */
2097 txn->error.precond = DAV_NEED_PRIVS;
2098 txn->error.resource = txn->req_tgt.path;
2099 txn->error.rights = DACL_READ;
2100 return HTTP_NO_PRIVS;
2101 }
2102
2103 if (record && record->uid) {
2104 /* GET on a resource */
2105 struct caldav_data *cdata = (struct caldav_data *) data;
2106 unsigned need_tz = 0;
2107 const char **hdr;
2108 icalcomponent *ical;
2109 int ret = HTTP_CONTINUE;
2110
2111 /* Check for optional CalDAV-Timezones header */
2112 hdr = spool_getheader(txn->req_hdrs, "CalDAV-Timezones");
2113 if (hdr && !strcmp(hdr[0], "T")) need_tz = 1;
2114
2115 if (cdata->comp_flags.tzbyref) {
2116 if (!cdata->organizer && cdata->sched_tag) {
2117 /* Resource has just had VTIMEZONEs stripped -
2118 check if conditional matches previous ETag */
2119
2120 if (check_precond(txn, cdata->sched_tag,
2121 record->internaldate) == HTTP_NOT_MODIFIED) {
2122 /* Fill in previous ETag and don't return Last-Modified */
2123 txn->resp_body.etag = cdata->sched_tag;
2124 txn->resp_body.lastmod = 0;
2125 ret = HTTP_NOT_MODIFIED;
2126 }
2127 }
2128 if (need_tz) {
2129 /* Add VTIMEZONE components for known TZIDs */
2130 struct timezone_rock tzrock = { NULL, NULL };
2131 icalcomponent *comp, *next;
2132 icalcomponent_kind kind;
2133
2134 *obj = ical = record_to_ical(mailbox, record, NULL);
2135 tzrock.new = ical;
2136
2137 comp = icalcomponent_get_first_real_component(ical);
2138 kind = icalcomponent_isa(comp);
2139 for (; comp; comp = next) {
2140 next = icalcomponent_get_next_component(ical, kind);
2141 icalcomponent_foreach_tzid(comp, &add_timezone, &tzrock);
2142 }
2143 }
2144 }
2145 else if (!need_tz && (namespace_calendar.allow & ALLOW_CAL_NOTZ)) {
2146 /* Strip known VTIMEZONEs */
2147 struct caldav_db *caldavdb = caldav_open_mailbox(mailbox);
2148 char *userid = NULL;
2149
2150 mailbox_unlock_index(mailbox, NULL);
2151 r = mailbox_lock_index(mailbox, LOCK_EXCLUSIVE);
2152 if (r) {
2153 syslog(LOG_ERR, "relock index(%s) failed: %s",
2154 mailbox->name, error_message(r));
2155 goto done;
2156 }
2157
2158 *obj = ical = record_to_ical(mailbox, record, &userid);
2159
2160 caldav_store_resource(txn, ical, mailbox,
2161 cdata->dav.resource, caldavdb,
2162 TZ_STRIP | (!cdata->sched_tag ? NEW_STAG : 0),
2163 userid);
2164 free(userid);
2165
2166 /* Fetch the new DAV and index records */
2167 /* NOTE: previous contents of cdata was freed by store_resource */
2168 caldav_lookup_resource(caldavdb, mailbox->name,
2169 txn->req_tgt.resource, &cdata, /*tombstones*/0);
2170
2171 mailbox_find_index_record(mailbox, cdata->dav.imap_uid, record);
2172
2173 /* Fill in new ETag and Last-Modified */
2174 txn->resp_body.etag = message_guid_encode(&record->guid);
2175 txn->resp_body.lastmod = record->internaldate;
2176
2177 caldav_close(caldavdb);
2178 }
2179
2180 /* iCalendar data in response should not be transformed */
2181 txn->flags.cc |= CC_NOTRANSFORM;
2182
2183 done:
2184 return ret;
2185 }
2186
2187 if (txn->req_tgt.mbentry->server) {
2188 /* Remote mailbox */
2189 struct backend *be;
2190
2191 be = proxy_findserver(txn->req_tgt.mbentry->server,
2192 &http_protocol, httpd_userid,
2193 &backend_cached, NULL, NULL, httpd_in);
2194 if (!be) return HTTP_UNAVAILABLE;
2195
2196 return http_pipe_req_resp(be, txn);
2197 }
2198
2199 /* Local Mailbox */
2200
2201 if (txn->req_tgt.collection) {
2202 /* Download an entire calendar collection */
2203 return export_calendar(txn);
2204 }
2205 else if (txn->req_tgt.userid) {
2206 /* GET a list of calendars under calendar-home-set */
2207 return list_calendars(txn);
2208 }
2209
2210 /* Unknown action */
2211 return HTTP_NO_CONTENT;
2212 }
2213
2214
2215 /* Perform a GET/HEAD request on a CalDAV/M-Attach resource */
meth_get_head_cal(struct transaction_t * txn,void * gparams)2216 static int meth_get_head_cal(struct transaction_t *txn,
2217 void *gparams __attribute__((unused)))
2218 {
2219 int r;
2220
2221 /* Parse the path */
2222 if ((r = caldav_parse_path(txn->req_uri->path,
2223 &txn->req_tgt, &txn->error.desc))) return r;
2224
2225 return meth_get_head(txn, (txn->req_tgt.flags == TGT_MANAGED_ATTACH) ?
2226 &webdav_params : &caldav_params);
2227 }
2228
2229 /* Decrement reference count on a managed attachment resource */
decrement_refcount(const char * managed_id,struct mailbox * attachments,struct webdav_db * webdavdb)2230 static void decrement_refcount(const char *managed_id,
2231 struct mailbox *attachments,
2232 struct webdav_db *webdavdb)
2233 {
2234 int r;
2235 struct webdav_data *wdata;
2236
2237 /* Find DAV record for the attachment with this managed-id */
2238 webdav_lookup_uid(webdavdb, managed_id, &wdata);
2239
2240 if (!wdata->dav.rowid) return;
2241
2242 if (!--wdata->ref_count) {
2243 /* Delete attachment resource */
2244 struct index_record record;
2245
2246 mailbox_find_index_record(attachments, wdata->dav.imap_uid, &record);
2247 record.system_flags |= FLAG_EXPUNGED;
2248
2249 r = mailbox_rewrite_index_record(attachments, &record);
2250
2251 if (r) {
2252 syslog(LOG_ERR, "expunging record (%s) failed: %s",
2253 attachments->name, error_message(r));
2254 }
2255 }
2256 else {
2257 /* Update reference count on WebDAV record */
2258 r = webdav_write(webdavdb, wdata);
2259
2260 if (r) {
2261 syslog(LOG_ERR, "updating ref count (%s) failed: %s",
2262 wdata->dav.resource, error_message(r));
2263 }
2264 }
2265 }
2266
2267 /* Increment reference count on a managed attachment resource */
increment_refcount(const char * managed_id,struct webdav_db * webdavdb)2268 static struct webdav_data *increment_refcount(const char *managed_id,
2269 struct webdav_db *webdavdb)
2270 {
2271 int r;
2272 struct webdav_data *wdata;
2273
2274 /* Find DAV record for the attachment with this managed-id */
2275 webdav_lookup_uid(webdavdb, managed_id, &wdata);
2276
2277 if (wdata->dav.rowid) {
2278 /* Update reference count on WebDAV record */
2279 wdata->ref_count++;
2280 r = webdav_write(webdavdb, wdata);
2281
2282 if (r) {
2283 syslog(LOG_ERR, "updating ref count (%s) failed: %s",
2284 wdata->dav.resource, error_message(r));
2285 }
2286 }
2287
2288 return wdata;
2289 }
2290
2291 /* Manage attachment */
caldav_post_attach(struct transaction_t * txn,int rights)2292 static int caldav_post_attach(struct transaction_t *txn, int rights)
2293 {
2294 int ret = 0, r, precond;
2295 struct resp_body_t *resp_body = &txn->resp_body;
2296 struct strlist *action, *mid, *rid;
2297 struct mime_type_t *mime = NULL;
2298 struct mailbox *calendar = NULL, *attachments = NULL;
2299 struct caldav_db *caldavdb = NULL;
2300 struct caldav_data *cdata;
2301 struct webdav_db *webdavdb = NULL;
2302 struct webdav_data *wdata;
2303 struct index_record record;
2304 char *schedule_address = NULL;
2305 const char *etag = NULL, **hdr;
2306 char *mailboxname = NULL;
2307 time_t lastmod = 0;
2308 icalcomponent *ical = NULL, *comp, *nextc, *master = NULL;
2309 icalcomponent_kind kind;
2310 icalproperty *aprop = NULL, *prop;
2311 icalparameter *param;
2312 unsigned op, return_rep;
2313 strarray_t *rids = NULL;
2314 enum {
2315 ATTACH_ADD,
2316 ATTACH_UPDATE,
2317 ATTACH_REMOVE
2318 };
2319
2320 if (!(namespace_calendar.allow & ALLOW_CAL_ATTACH)) return HTTP_NOT_ALLOWED;
2321
2322 /* Check ACL for current user */
2323 if (!(rights & DACL_WRITECONT)) {
2324 /* DAV:need-privileges */
2325 txn->error.precond = DAV_NEED_PRIVS;
2326 txn->error.resource = txn->req_tgt.path;
2327 txn->error.rights = DACL_WRITECONT;
2328 return HTTP_NO_PRIVS;
2329 }
2330
2331 if ((return_rep = (get_preferences(txn) & PREFER_REP))) {
2332 /* Check requested MIME type:
2333 1st entry in gparams->mime_types array MUST be default MIME type */
2334 if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
2335 mime = get_accept_type(hdr, caldav_mime_types);
2336 else mime = caldav_mime_types;
2337 if (!mime) return HTTP_NOT_ACCEPTABLE;
2338 }
2339
2340 /* Fetch and sanity check parameters */
2341 action = hash_lookup("action", &txn->req_qparams);
2342 mid = hash_lookup("managed-id", &txn->req_qparams);
2343 rid = hash_lookup("rid", &txn->req_qparams);
2344
2345 if (!action || action->next) return HTTP_BAD_REQUEST;
2346 else if (!strcmp(action->s, "attachment-add")) {
2347 op = ATTACH_ADD;
2348 if (mid) return HTTP_BAD_REQUEST;
2349 }
2350 else if (!strcmp(action->s, "attachment-update")) {
2351 op = ATTACH_UPDATE;
2352 if (rid || !mid || mid->next) return HTTP_BAD_REQUEST;
2353 }
2354 else if (!strcmp(action->s, "attachment-remove")) {
2355 op = ATTACH_REMOVE;
2356 if (!mid || mid->next) return HTTP_BAD_REQUEST;
2357 }
2358 else return HTTP_BAD_REQUEST;
2359
2360 /* Open calendar for writing */
2361 r = mailbox_open_iwl(txn->req_tgt.mbentry->name, &calendar);
2362 if (r) {
2363 syslog(LOG_ERR, "mailbox_open_iwl(%s) failed: %s",
2364 txn->req_tgt.mbentry->name, error_message(r));
2365 txn->error.desc = error_message(r);
2366 ret = HTTP_SERVER_ERROR;
2367 goto done;
2368 }
2369
2370 /* Open the CalDAV DB corresponding to the calendar */
2371 caldavdb = caldav_open_mailbox(calendar);
2372
2373 /* Find message UID for the cal resource */
2374 caldav_lookup_resource(caldavdb, txn->req_tgt.mbentry->name,
2375 txn->req_tgt.resource, &cdata, 0);
2376 if (!cdata->dav.rowid) ret = HTTP_NOT_FOUND;
2377 else if (!cdata->dav.imap_uid) ret = HTTP_CONFLICT;
2378 if (ret) goto done;
2379
2380 /* Fetch index record for the cal resource */
2381 memset(&record, 0, sizeof(struct index_record));
2382 r = mailbox_find_index_record(calendar, cdata->dav.imap_uid, &record);
2383 if (r) {
2384 txn->error.desc = error_message(r);
2385 ret = HTTP_SERVER_ERROR;
2386 goto done;
2387 }
2388
2389 etag = message_guid_encode(&record.guid);
2390 lastmod = record.internaldate;
2391
2392 /* Load and parse message containing the resource */
2393 ical = record_to_ical(calendar, &record, &schedule_address);
2394 comp = icalcomponent_get_first_real_component(ical);
2395 kind = icalcomponent_isa(comp);
2396
2397 /* Check any preconditions */
2398 precond = caldav_check_precond(txn, &caldav_params,
2399 calendar, cdata, etag, lastmod);
2400
2401 switch (precond) {
2402 case HTTP_OK:
2403 break;
2404
2405 case HTTP_LOCKED:
2406 txn->error.precond = DAV_NEED_LOCK_TOKEN;
2407 txn->error.resource = txn->req_tgt.path;
2408
2409 GCC_FALLTHROUGH
2410
2411 default:
2412 /* We failed a precondition - don't perform the request */
2413 ret = precond;
2414
2415 if ((precond == HTTP_PRECOND_FAILED) && return_rep) goto return_rep;
2416 else goto done;
2417 }
2418
2419 /* Open attachments collection for writing */
2420 mailboxname = caldav_mboxname(httpd_userid, MANAGED_ATTACH);
2421 r = mailbox_open_iwl(mailboxname, &attachments);
2422 if (r) {
2423 syslog(LOG_ERR, "mailbox_open_iwl(%s) failed: %s",
2424 mailboxname, error_message(r));
2425 txn->error.desc = error_message(r);
2426 ret = HTTP_SERVER_ERROR;
2427 free(mailboxname);
2428 goto done;
2429 }
2430 free(mailboxname);
2431
2432 /* Open the WebDAV DB corresponding to the attachments collection */
2433 webdavdb = webdav_open_mailbox(attachments);
2434
2435 if (mid) {
2436 /* Locate first ATTACH property with this MANAGED-ID */
2437 do {
2438 for (aprop = icalcomponent_get_first_property(comp,
2439 ICAL_ATTACH_PROPERTY);
2440 aprop;
2441 aprop = icalcomponent_get_next_property(comp,
2442 ICAL_ATTACH_PROPERTY)) {
2443 param = icalproperty_get_managedid_parameter(aprop);
2444 if (param &&
2445 !strcmp(mid->s, icalparameter_get_managedid(param))) break;
2446 }
2447
2448 /* Check if this is master component */
2449 if (rid && !master &&
2450 !icalcomponent_get_first_property(comp,
2451 ICAL_RECURRENCEID_PROPERTY)) {
2452 master = comp;
2453 }
2454
2455 } while (!aprop &&
2456 (comp = icalcomponent_get_next_component(ical, kind)));
2457
2458 if (!aprop) {
2459 txn->error.precond = CALDAV_VALID_MANAGEDID;
2460 ret = HTTP_BAD_REQUEST;
2461 goto done;
2462 }
2463
2464 /* Update reference count */
2465 decrement_refcount(mid->s, attachments, webdavdb);
2466 }
2467
2468 if (op == ATTACH_REMOVE) aprop = NULL;
2469 else {
2470 /* SHA1 of content used as resource UID, resource name, & managed-id */
2471 static char uid[2*MESSAGE_GUID_SIZE+1];
2472 struct message_guid guid;
2473
2474 /* Read body */
2475 txn->req_body.flags |= BODY_DECODE;
2476 r = http_read_body(httpd_in, httpd_out,
2477 txn->req_hdrs, &txn->req_body, &txn->error.desc);
2478 if (r) {
2479 txn->flags.conn = CONN_CLOSE;
2480 return r;
2481 }
2482
2483 /* Make sure we have a body */
2484 if (!buf_len(&txn->req_body.payload)) {
2485 txn->error.desc = "Missing request body";
2486 ret = HTTP_BAD_REQUEST;
2487 goto done;
2488 }
2489
2490 /* Generate UID of body content */
2491 message_guid_generate(&guid, buf_base(&txn->req_body.payload),
2492 buf_len(&txn->req_body.payload));
2493 strcpy(uid, message_guid_encode(&guid));
2494
2495 /* Store the new/updated attachment using WebDAV callback */
2496 ret = webdav_params.put.proc(txn, &txn->req_body.payload,
2497 attachments, uid, webdavdb, 0);
2498
2499 switch (ret) {
2500 case HTTP_CREATED:
2501 case HTTP_NO_CONTENT:
2502 resp_body->cmid = uid;
2503 resp_body->etag = NULL;
2504 break;
2505
2506 default:
2507 goto done;
2508 break;
2509 }
2510
2511 /* Update reference count */
2512 wdata = increment_refcount(uid, webdavdb);
2513
2514 /* Create new ATTACH property */
2515 if (aprop) aprop = icalproperty_new_clone(aprop);
2516 else {
2517 const char *proto = NULL, *host = NULL;
2518 icalattach *attach;
2519
2520 assert(!buf_len(&txn->buf));
2521 http_proto_host(txn->req_hdrs, &proto, &host);
2522 buf_printf(&txn->buf, "%s://%s%s/%s/%s/%s%s",
2523 proto, host, namespace_calendar.prefix,
2524 USER_COLLECTION_PREFIX,
2525 txn->req_tgt.userid, MANAGED_ATTACH, uid);
2526 attach = icalattach_new_from_url(buf_cstring(&txn->buf));
2527 buf_reset(&txn->buf);
2528
2529 aprop = icalproperty_new_attach(attach);
2530 icalattach_unref(attach);
2531 }
2532
2533 /* Update ATTACH parameters - MANAGED-ID, FILENAME, SIZE, FMTTYPE */
2534 param = icalproperty_get_managedid_parameter(aprop);
2535 if (param) icalparameter_set_managedid(param, resp_body->cmid);
2536 else {
2537 param = icalparameter_new_managedid(resp_body->cmid);
2538 icalproperty_add_parameter(aprop, param);
2539 }
2540
2541 if (wdata->filename) {
2542 param = icalproperty_get_filename_parameter(aprop);
2543 if (param) icalparameter_set_filename(param, wdata->filename);
2544 else {
2545 param = icalparameter_new_filename(wdata->filename);
2546 icalproperty_add_parameter(aprop, param);
2547 }
2548 }
2549
2550 assert(!buf_len(&txn->buf));
2551 buf_printf(&txn->buf, "%tu", buf_len(&txn->req_body.payload));
2552 param = icalproperty_get_size_parameter(aprop);
2553 if (param) icalparameter_set_size(param, buf_cstring(&txn->buf));
2554 else {
2555 param = icalparameter_new_size(buf_cstring(&txn->buf));
2556 icalproperty_add_parameter(aprop, param);
2557 }
2558 buf_reset(&txn->buf);
2559
2560 if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) {
2561 param = icalproperty_get_first_parameter(aprop,
2562 ICAL_FMTTYPE_PARAMETER);
2563 if (param) icalparameter_set_fmttype(param, *hdr);
2564 else {
2565 param = icalparameter_new_fmttype(*hdr);
2566 icalproperty_add_parameter(aprop, param);
2567 }
2568 }
2569 }
2570
2571 if (rid) {
2572 /* Split list of RECURRENCE-IDs */
2573 rids = strarray_split(rid->s, ",", STRARRAY_TRIM);
2574 }
2575
2576 /* Process each component */
2577 for (; comp; comp = nextc) {
2578 int idx;
2579
2580 nextc = icalcomponent_get_next_component(ical, kind);
2581
2582 if (rid) {
2583 /* Check if RECURRENCE-ID is in our list */
2584 const char *recurid;
2585
2586 prop = icalcomponent_get_first_property(comp,
2587 ICAL_RECURRENCEID_PROPERTY);
2588 if (prop) recurid = icalproperty_get_value_as_string(prop);
2589 else {
2590 master = comp;
2591 recurid = "M";
2592 }
2593
2594 idx = strarray_find_case(rids, recurid, 0);
2595 if (idx >= 0) {
2596 /* Remove found recurid from list -
2597 we will create new overrides for unfound recurids */
2598 free(strarray_remove(rids, idx));
2599 }
2600 else if (!nextc && strarray_size(rids)) {
2601 /* Create new overrides */
2602 struct icaldatetimeperiodtype dtp;
2603 icalproperty *nextp;
2604
2605 master = icalcomponent_new_clone(master);
2606
2607 /* Get DTSTART and Remove unwanted recurrence properties */
2608 for (prop = icalcomponent_get_first_property(master,
2609 ICAL_ANY_PROPERTY);
2610 prop; prop = nextp) {
2611 nextp = icalcomponent_get_next_property(master,
2612 ICAL_ANY_PROPERTY);
2613 switch (icalproperty_isa(prop)) {
2614 case ICAL_RRULE_PROPERTY:
2615 case ICAL_RDATE_PROPERTY:
2616 case ICAL_EXDATE_PROPERTY:
2617 case ICAL_EXRULE_PROPERTY:
2618 icalcomponent_remove_property(master, prop);
2619 icalproperty_free(prop);
2620 break;
2621
2622 case ICAL_DTSTART_PROPERTY:
2623 dtp = icalproperty_get_datetimeperiod(prop);
2624 break;
2625
2626 default:
2627 break;
2628 }
2629 }
2630
2631 /* Get TZID of DTSTART */
2632 struct icaltimetype dtstart = dtp.time;
2633 const icaltimezone *tz = icaltime_get_timezone(dtstart);
2634 const char *tzid = icaltimezone_get_tzid((icaltimezone *) tz);
2635
2636 for (idx = 0; idx < strarray_size(rids); idx++) {
2637 /* Create new component and set DTSTART and RECURRENCE-ID */
2638 dtstart = icaltime_from_string(strarray_nth(rids, idx));
2639 if (icaltime_is_null_time(dtstart)) continue;
2640
2641 icaltime_set_timezone(&dtstart, tz);
2642
2643 comp = icalcomponent_new_clone(master);
2644 icalcomponent_add_component(ical, comp);
2645 icalcomponent_set_dtstart(comp, dtstart);
2646
2647 prop = icalproperty_new_recurrenceid(dtstart);
2648 icalcomponent_add_property(comp, prop);
2649 if (tzid) {
2650 icalproperty_add_parameter(prop,
2651 icalparameter_new_tzid(tzid));
2652 }
2653 }
2654
2655 icalcomponent_free(master);
2656 nextc = icalcomponent_get_next_component(ical, kind);
2657 rid = NULL;
2658 }
2659 else {
2660 /* No matching RECURRENCE-ID - Skip this component */
2661 continue;
2662 }
2663 }
2664
2665 if (mid) {
2666 /* Remove matching ATTACH property */
2667 for (prop = icalcomponent_get_first_property(comp,
2668 ICAL_ATTACH_PROPERTY);
2669 prop;
2670 prop = icalcomponent_get_next_property(comp,
2671 ICAL_ATTACH_PROPERTY)) {
2672 param = icalproperty_get_managedid_parameter(prop);
2673 if (param &&
2674 !strcmp(mid->s, icalparameter_get_managedid(param))) {
2675 icalcomponent_remove_property(comp, prop);
2676 icalproperty_free(prop);
2677 break;
2678 }
2679 }
2680
2681 if (!prop) {
2682 /* No matching ATTACH - Skip this component */
2683 continue;
2684 }
2685 }
2686
2687 if (aprop) {
2688 /* Add new/updated ATTACH property */
2689 icalcomponent_add_property(comp, icalproperty_new_clone(aprop));
2690 }
2691 }
2692
2693 /* Finished with attachment collection */
2694 mailbox_unlock_index(attachments, NULL);
2695
2696 /* Store updated calendar resource */
2697 ret = caldav_store_resource(txn, ical, calendar, txn->req_tgt.resource,
2698 caldavdb, return_rep, schedule_address);
2699
2700 if (ret == HTTP_NO_CONTENT && return_rep) {
2701 struct buf *data;
2702
2703 ret = (op == ATTACH_ADD) ? HTTP_CREATED : HTTP_OK;
2704
2705 return_rep:
2706 /* Convert into requested MIME type */
2707 data = mime->from_object(ical);
2708
2709 /* Fill in Content-Type, Content-Length */
2710 resp_body->type = mime->content_type;
2711 resp_body->len = buf_len(data);
2712
2713 /* Fill in Content-Location */
2714 resp_body->loc = txn->req_tgt.path;
2715
2716 /* Fill in Expires and Cache-Control */
2717 resp_body->maxage = 3600; /* 1 hr */
2718 txn->flags.cc = CC_MAXAGE
2719 | CC_REVALIDATE /* don't use stale data */
2720 | CC_NOTRANSFORM; /* don't alter iCal data */
2721
2722 /* Output current representation */
2723 write_body(ret, txn, buf_base(data), buf_len(data));
2724
2725 buf_destroy(data);
2726 ret = 0;
2727 }
2728
2729 done:
2730 strarray_free(rids);
2731 free(schedule_address);
2732 if (aprop) icalproperty_free(aprop);
2733 if (ical) icalcomponent_free(ical);
2734 if (webdavdb) webdav_close(webdavdb);
2735 if (caldavdb) caldav_close(caldavdb);
2736 mailbox_close(&attachments);
2737 mailbox_close(&calendar);
2738
2739 return ret;
2740 }
2741
2742
2743 /* Perform a busy time request */
caldav_post_outbox(struct transaction_t * txn,int rights)2744 static int caldav_post_outbox(struct transaction_t *txn, int rights)
2745 {
2746 int ret = 0, r;
2747 const char **hdr;
2748 struct mime_type_t *mime = NULL;
2749 icalcomponent *ical = NULL, *comp;
2750 icalcomponent_kind kind = 0;
2751 icalproperty_method meth = 0;
2752 icalproperty *prop = NULL;
2753 const char *uid = NULL, *organizer = NULL;
2754 struct caldav_sched_param sparam;
2755
2756 /* Check Content-Type */
2757 if ((hdr = spool_getheader(txn->req_hdrs, "Content-Type"))) {
2758 for (mime = caldav_mime_types; mime->content_type; mime++) {
2759 if (is_mediatype(mime->content_type, hdr[0])) break;
2760 }
2761 }
2762 if (!mime || !mime->content_type) {
2763 txn->error.precond = CALDAV_SUPP_DATA;
2764 return HTTP_BAD_REQUEST;
2765 }
2766
2767 /* Read body */
2768 txn->req_body.flags |= BODY_DECODE;
2769 r = http_read_body(httpd_in, httpd_out,
2770 txn->req_hdrs, &txn->req_body, &txn->error.desc);
2771 if (r) {
2772 txn->flags.conn = CONN_CLOSE;
2773 return r;
2774 }
2775
2776 /* Make sure we have a body */
2777 if (!buf_len(&txn->req_body.payload)) {
2778 txn->error.desc = "Missing request body\r\n";
2779 return HTTP_BAD_REQUEST;
2780 }
2781
2782 /* Parse the iCal data for important properties */
2783 ical = mime->to_object(&txn->req_body.payload);
2784 if (!ical || !icalrestriction_check(ical)) {
2785 txn->error.precond = CALDAV_VALID_DATA;
2786 ret = HTTP_BAD_REQUEST;
2787 goto done;
2788 }
2789
2790 meth = icalcomponent_get_method(ical);
2791 comp = icalcomponent_get_first_real_component(ical);
2792 if (comp) {
2793 uid = icalcomponent_get_uid(comp);
2794 kind = icalcomponent_isa(comp);
2795 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
2796 }
2797
2798 /* Check method preconditions */
2799 if (!meth || !uid || !prop) {
2800 txn->error.precond = CALDAV_VALID_SCHED;
2801 ret = HTTP_BAD_REQUEST;
2802 goto done;
2803 }
2804
2805 /* Organizer MUST be local to use CalDAV Scheduling */
2806 organizer = icalproperty_get_organizer(prop);
2807 if (!organizer) {
2808 txn->error.precond = CALDAV_VALID_ORGANIZER;
2809 ret = HTTP_FORBIDDEN;
2810 goto done;
2811 }
2812 r = caladdress_lookup(organizer, &sparam, txn->req_tgt.userid);
2813 if (r) {
2814 txn->error.precond = CALDAV_VALID_ORGANIZER;
2815 ret = HTTP_FORBIDDEN;
2816 goto done;
2817 }
2818 if (!sparam.isyou) {
2819 sched_param_free(&sparam);
2820 txn->error.precond = CALDAV_VALID_ORGANIZER;
2821 ret = HTTP_FORBIDDEN;
2822 goto done;
2823 }
2824 sched_param_free(&sparam);
2825
2826 switch (kind) {
2827 case ICAL_VFREEBUSY_COMPONENT:
2828 if (meth == ICAL_METHOD_REQUEST)
2829 if (!(rights & DACL_SCHEDFB)) {
2830 /* DAV:need-privileges */
2831 txn->error.precond = DAV_NEED_PRIVS;
2832 txn->error.resource = txn->req_tgt.path;
2833 txn->error.rights = DACL_SCHEDFB;
2834 ret = HTTP_NO_PRIVS;
2835 }
2836 else ret = sched_busytime_query(txn, mime, ical);
2837 else {
2838 txn->error.precond = CALDAV_VALID_SCHED;
2839 ret = HTTP_BAD_REQUEST;
2840 }
2841 break;
2842
2843 default:
2844 txn->error.precond = CALDAV_VALID_SCHED;
2845 ret = HTTP_BAD_REQUEST;
2846 }
2847
2848 done:
2849 if (ical) icalcomponent_free(ical);
2850
2851 return ret;
2852 }
2853
2854
2855 /* Perform a bulk import */
caldav_import(struct transaction_t * txn,void * obj,struct mailbox * mailbox,void * destdb,xmlNodePtr root,xmlNsPtr * ns,unsigned flags)2856 static int caldav_import(struct transaction_t *txn, void *obj,
2857 struct mailbox *mailbox, void *destdb,
2858 xmlNodePtr root, xmlNsPtr *ns, unsigned flags)
2859 {
2860 int ret = 0;
2861 icalcomponent *ical = obj, *comp, *next;
2862 icalcomponent_kind kind;
2863 icalproperty *prodid, *version, *calscale;
2864 const char *uid;
2865 struct caldav_db *caldavdb = destdb;
2866 xmlNodePtr resp, propstat, prop, error;
2867 unsigned post_count = 0;
2868
2869 if (!root) {
2870 /* Validate the iCal data */
2871 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
2872 txn->error.precond = CALDAV_VALID_DATA;
2873 return HTTP_FORBIDDEN;
2874 }
2875 icalrestriction_check(ical);
2876 if ((txn->error.desc = get_icalcomponent_errstr(ical))) {
2877 buf_setcstr(&txn->buf, txn->error.desc);
2878 txn->error.desc = buf_cstring(&txn->buf);
2879 txn->error.precond = CALDAV_VALID_DATA;
2880 return HTTP_FORBIDDEN;
2881 }
2882
2883 /* Make sure each "real" component has a UID */
2884 for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
2885 comp;
2886 comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) {
2887
2888 icalcomponent_kind kind = icalcomponent_isa(comp);
2889
2890 if (kind == ICAL_VTIMEZONE_COMPONENT) continue;
2891
2892 uid = icalcomponent_get_uid(comp);
2893 if (!uid) {
2894 buf_reset(&txn->buf);
2895 buf_printf(&txn->buf, "Missing UID property in %s",
2896 icalcomponent_kind_to_string(kind));
2897 txn->error.desc = buf_cstring(&txn->buf);
2898 txn->error.precond = CALDAV_VALID_DATA;
2899 return HTTP_FORBIDDEN;
2900 }
2901 }
2902
2903 return 0;
2904 }
2905
2906 ensure_ns(ns, NS_CALDAV, root, XML_NS_CALDAV, "C");
2907
2908 size_t len = strlen(txn->req_tgt.path);
2909 txn->req_tgt.resource = txn->req_tgt.path + len;
2910
2911 prodid = icalcomponent_get_first_property(ical, ICAL_PRODID_PROPERTY);
2912 version = icalcomponent_get_first_property(ical, ICAL_VERSION_PROPERTY);
2913 calscale = icalcomponent_get_first_property(ical, ICAL_CALSCALE_PROPERTY);
2914
2915 for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
2916 comp; comp = next) {
2917 icalcomponent *newical;
2918 struct timezone_rock tzrock;
2919
2920 next = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT);
2921 kind = icalcomponent_isa(comp);
2922 if (kind == ICAL_VTIMEZONE_COMPONENT) continue;
2923
2924 /* Create new object, making copies of PRODID, VERSION, CALSCALE */
2925 newical = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
2926 if (prodid)
2927 icalcomponent_add_property(newical, icalproperty_new_clone(prodid));
2928 if (version)
2929 icalcomponent_add_property(newical, icalproperty_new_clone(version));
2930 if (calscale)
2931 icalcomponent_add_property(newical, icalproperty_new_clone(calscale));
2932
2933 /* Add our component */
2934 icalcomponent_remove_component(ical, comp);
2935 icalcomponent_add_component(newical, comp);
2936
2937 /* Look for matching UIDs (recurrence set) */
2938
2939 /* Add required timezone components */
2940 tzrock.old = ical;
2941 tzrock.new = newical;
2942 icalcomponent_foreach_tzid(comp, &add_timezone, &tzrock);
2943
2944 /* Append a unique resource name to URL and perform a PUT */
2945 uid = icalcomponent_get_uid(comp);
2946 txn->req_tgt.reslen =
2947 snprintf(txn->req_tgt.resource, MAX_MAILBOX_PATH - len,
2948 "%x-%d-%ld-%u.ics",
2949 strhash(uid), getpid(), time(0), post_count++);
2950
2951 ret = caldav_put(txn, newical, mailbox,
2952 txn->req_tgt.resource, caldavdb, flags);
2953
2954 resp = xmlNewChild(root, ns[NS_DAV], BAD_CAST "response", NULL);
2955 if (!resp) {
2956 txn->error.desc = "Unable to add response XML element";
2957 return HTTP_SERVER_ERROR;
2958 }
2959
2960 switch (ret) {
2961 case HTTP_OK:
2962 case HTTP_CREATED:
2963 case HTTP_NO_CONTENT:
2964 xml_add_href(resp, NULL, txn->req_tgt.path);
2965 propstat = xmlNewChild(resp, ns[NS_DAV], BAD_CAST "propstat", NULL);
2966 prop = xmlNewChild(propstat, ns[NS_DAV], BAD_CAST "prop", NULL);
2967
2968 if (txn->resp_body.etag) {
2969 xmlNewTextChild(prop, ns[NS_DAV], BAD_CAST "getetag",
2970 BAD_CAST txn->resp_body.etag);
2971 }
2972 if (flags & PREFER_REP) {
2973 xmlNodePtr data = xmlNewChild(prop, ns[NS_CALDAV],
2974 BAD_CAST "calendar-data", NULL);
2975 const char *icalstr = icalcomponent_as_ical_string(newical);
2976 xmlAddChild(data, xmlNewCDataBlock(root->doc, BAD_CAST icalstr,
2977 strlen(icalstr)));
2978 }
2979 else xmlNewTextChild(prop, ns[NS_CS], BAD_CAST "uid", BAD_CAST uid);
2980
2981 xmlNewChild(propstat, ns[NS_DAV], BAD_CAST "status",
2982 BAD_CAST http_statusline(HTTP_OK));
2983 break;
2984
2985 default:
2986 xml_add_href(resp, NULL, NULL);
2987 xmlNewChild(resp, ns[NS_DAV], BAD_CAST "status",
2988 BAD_CAST http_statusline(ret));
2989 error = xml_add_error(resp, &txn->error, ns);
2990 xmlNewTextChild(error, ns[NS_CS], BAD_CAST "uid", BAD_CAST uid);
2991 break;
2992 }
2993
2994 icalcomponent_free(newical);
2995 }
2996
2997 return 0;
2998 }
2999
3000
caldav_post(struct transaction_t * txn)3001 static int caldav_post(struct transaction_t *txn)
3002 {
3003 int ret, rights;
3004
3005 /* Get rights for current user */
3006 rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
3007
3008 if (txn->req_tgt.resource) {
3009 if (txn->req_tgt.flags) {
3010 /* Don't allow POST on resources in special collections */
3011 ret = HTTP_NOT_ALLOWED;
3012 }
3013 else if (txn->req_tgt.mbentry->server) {
3014 /* Remote mailbox */
3015 struct backend *be;
3016
3017 be = proxy_findserver(txn->req_tgt.mbentry->server,
3018 &http_protocol, httpd_userid,
3019 &backend_cached, NULL, NULL, httpd_in);
3020 if (!be) ret = HTTP_UNAVAILABLE;
3021 else ret = http_pipe_req_resp(be, txn);
3022 }
3023 else {
3024 /* Local Mailbox */
3025 ret = caldav_post_attach(txn, rights);
3026 }
3027 }
3028 else if (txn->req_tgt.flags == TGT_SCHED_OUTBOX) {
3029 /* POST to schedule-outbox */
3030 ret = caldav_post_outbox(txn, rights);
3031 }
3032 else if (txn->req_tgt.flags) {
3033 /* Don't allow POST to special collections */
3034 ret = HTTP_NOT_ALLOWED;
3035 }
3036 else if (!txn->req_tgt.collection) {
3037 /* POST to calendar-home-set */
3038 ret = notify_post(txn);
3039 }
3040 else {
3041 /* POST to regular calendar collection */
3042 ret = HTTP_CONTINUE;
3043 }
3044
3045 return ret;
3046 }
3047
3048
3049 #ifdef HAVE_VPATCH
3050 enum {
3051 ACTION_UPDATE = 1,
3052 ACTION_DELETE,
3053 ACTION_SETPARAM
3054 };
3055
3056 enum {
3057 SEGMENT_COMP = 1,
3058 SEGMENT_PROP,
3059 SEGMENT_PARAM
3060 };
3061
3062 union match_criteria_t {
3063 struct {
3064 char *uid; /* component UID (optional) */
3065 icaltimetype rid; /* component RECURRENCE-ID (optional) */
3066 } comp;
3067 struct {
3068 char *param; /* parameter name (optional) */
3069 char *value; /* prop/param value (optional) */
3070 unsigned not:1; /* not equal? */
3071 } prop;
3072 };
3073
3074 struct path_segment_t {
3075 unsigned type; /* Is it comp, prop, or param segment? */
3076 unsigned kind; /* libical kind of comp, prop, or param */
3077 union match_criteria_t match; /* match criteria (depends on 'type') */
3078 unsigned action; /* patch action (create,update,setparam)*/
3079 void *data; /* patch data (depends on 'action') */
3080
3081 struct path_segment_t *sibling;
3082 struct path_segment_t *child;
3083 };
3084
3085 struct patch_data_t {
3086 icalcomponent *patch; /* component containg patch data */
3087 struct path_segment_t *delete; /* list of PATCH-DELETE actions */
3088 struct path_segment_t *setparam; /* list of PATCH-PARAMETER items */
3089 };
3090
parse_target_path(char * path,struct path_segment_t ** path_seg,unsigned action,void * data,struct error_t * err)3091 static int parse_target_path(char *path, struct path_segment_t **path_seg,
3092 unsigned action, void *data,
3093 struct error_t *err)
3094 {
3095 char *p, sep;
3096 struct path_segment_t *tail = NULL, *new;
3097
3098 for (sep = *path++; sep == '/';) {
3099 p = path + strcspn(path, "[/#");
3100 if ((sep = *p)) *p++ = '\0';
3101
3102 new = xzmalloc(sizeof(struct path_segment_t));
3103 new->type = SEGMENT_COMP;
3104 new->kind = icalcomponent_string_to_kind(path);
3105 /* Initialize RID as invalid time rather than NULL time
3106 since NULL time is used for empty RID (master component) */
3107 new->match.comp.rid.year = -1;
3108
3109 if (!*path_seg) *path_seg = new;
3110 else tail->child = new;
3111 tail = new;
3112
3113 path = p;
3114
3115 if (sep == '[') {
3116 /* Parse comp-match */
3117 const char *prefix = "UID=";
3118 size_t prefix_len = strlen(prefix);
3119
3120 if (!(p = strchr(path, ']'))) {
3121 err->desc = "Badly formatted comp-match";
3122 return HTTP_BAD_REQUEST;
3123 }
3124
3125 /* Parse uid-match */
3126 if (!strncmp(path, prefix, prefix_len)) {
3127 path += prefix_len;
3128 *p++ = '\0';
3129 new->match.comp.uid = xstrdup(path);
3130 sep = *p++;
3131 path = p;
3132 }
3133
3134 /* Parse rid-match */
3135 if (sep == '[') {
3136 prefix = "RID=";
3137 prefix_len = strlen(prefix);
3138
3139 if (strncmp(path, prefix, prefix_len) ||
3140 !(p = strchr(path, ']'))) {
3141 err->desc = "Badly formatted rid-match";
3142 return HTTP_BAD_REQUEST;
3143 }
3144
3145 path += prefix_len;
3146 *p++ = '\0';
3147 if (*path && strcmp(path, "M")) {
3148 new->match.comp.rid = icaltime_from_string(path);
3149 if (icaltime_is_null_time(new->match.comp.rid)) {
3150 err->desc = "Invalid recurrence-id";
3151 return HTTP_BAD_REQUEST;
3152 }
3153 }
3154 else new->match.comp.rid = icaltime_null_time();
3155
3156 sep = *p++;
3157 path = p;
3158 }
3159 }
3160 }
3161
3162 if (sep == '#' && !*path_seg) {
3163 /* Parse prop-segment */
3164 p = path + strcspn(path, "[;=");
3165 if ((sep = *p)) *p++ = '\0';
3166
3167 new = xzmalloc(sizeof(struct path_segment_t));
3168 new->type = SEGMENT_PROP;
3169 new->kind = icalproperty_string_to_kind(path);
3170
3171 if (!*path_seg) *path_seg = new;
3172 else tail->child = new;
3173 tail = new;
3174
3175 path = p;
3176
3177 if (sep == '[') {
3178 /* Parse prop-match (MUST start with '=' or '!' or '@') */
3179 if (strspn(path, "=!@") != 1 || !(p = strchr(path, ']'))) {
3180 err->desc = "Badly formatted prop-match";
3181 return HTTP_BAD_REQUEST;
3182 }
3183
3184 *p++ = '\0';
3185 if (*path == '@') {
3186 /* Parse param-match */
3187 size_t namelen = strcspn(++path, "!=");
3188 new->match.prop.param = xstrndup(path, namelen);
3189 path += namelen;
3190 }
3191
3192 if (*path) {
3193 /* Parse prop/param [not]equal value */
3194 if (*path++ == '!') new->match.prop.not = 1;
3195 new->match.prop.value = xstrdup(path);
3196 }
3197
3198 sep = *p++;
3199 path = p;
3200 }
3201
3202 if (sep == ';') {
3203 /* Parse param-segment */
3204 p = path + strcspn(path, "=");
3205 if ((sep = *p)) *p++ = '\0';
3206
3207 new = xzmalloc(sizeof(struct path_segment_t));
3208 new->type = SEGMENT_PARAM;
3209 new->kind = icalparameter_string_to_kind(path);
3210
3211 tail->child = new;
3212 tail = new;
3213
3214 path = p;
3215 }
3216
3217 if (sep == '=' && action == ACTION_DELETE) {
3218 /* Parse value-segment */
3219 new->data = xstrdup(path);
3220 }
3221 else if (sep != '\0') {
3222 err->desc = "Invalid separator following prop-segment";
3223 return HTTP_BAD_REQUEST;
3224 }
3225 }
3226 else if (sep != '\0') {
3227 err->desc = "Invalid separator following comp-segment";
3228 return HTTP_BAD_REQUEST;
3229 }
3230
3231 tail->action = action;
3232 if (!tail->data) tail->data = data;
3233
3234 return 0;
3235 }
3236
3237 static void apply_patch(struct path_segment_t *path_seg,
3238 void *parent, int *num_changes);
3239
remove_single_value(const char * oldstr,const char * single)3240 static char *remove_single_value(const char *oldstr, const char *single)
3241 {
3242 char *newstr = NULL;
3243 strarray_t *values = strarray_split(oldstr, ",", STRARRAY_TRIM);
3244 int idx = strarray_find(values, single, 0);
3245
3246 if (idx >= 0) {
3247 /* Found the single value, remove it, and create new string */
3248 strarray_remove(values, idx);
3249 newstr = strarray_join(values, ",");
3250 }
3251 strarray_free(values);
3252
3253 return newstr;
3254 }
3255
3256 /* Apply a patch action to a parameter segment */
apply_patch_parameter(struct path_segment_t * path_seg,icalproperty * parent,int * num_changes)3257 static void apply_patch_parameter(struct path_segment_t *path_seg,
3258 icalproperty *parent, int *num_changes)
3259 {
3260 icalparameter *param =
3261 icalproperty_get_first_parameter(parent, path_seg->kind);
3262 if (!param) return;
3263
3264 if (path_seg->action == ACTION_DELETE) {
3265 switch (path_seg->kind) {
3266 case ICAL_MEMBER_PARAMETER:
3267 /* Multi-valued parameter */
3268 if (path_seg->data) {
3269 /* Check if entire parameter value == single value */
3270 const char *single = (const char *) path_seg->data;
3271 const char *param_val = icalparameter_get_value_as_string(param);
3272
3273 if (strcmp(param_val, single)) {
3274 /* Not an exact match, try to remove single value */
3275 char *newval = remove_single_value(param_val, single);
3276 if (newval) {
3277 *num_changes += 1;
3278 icalparameter_set_member(param, newval);
3279 free(newval);
3280 }
3281 break;
3282 }
3283 }
3284
3285 /* Fall through and delete entire parameter */
3286 GCC_FALLTHROUGH
3287
3288 default:
3289 *num_changes += 1;
3290 icalproperty_remove_parameter_by_ref(parent, param);
3291 break;
3292 }
3293 }
3294 }
3295
apply_param_match(icalproperty * prop,union match_criteria_t * match)3296 static int apply_param_match(icalproperty *prop, union match_criteria_t *match)
3297 {
3298 icalparameter_kind kind;
3299 icalparameter *param;
3300 int ret = 1;
3301
3302 /* XXX Need to handle X- parameters */
3303
3304 kind = icalparameter_string_to_kind(match->prop.param);
3305 param = icalproperty_get_first_parameter(prop, kind);
3306 if (!param) {
3307 /* property doesn't have this parameter */
3308 ret = match->prop.not;
3309 }
3310 else if (match->prop.value) {
3311 const char *param_val = icalparameter_get_value_as_string(param);
3312
3313 ret = !strcmp(match->prop.value, param_val);
3314 if (match->prop.not) ret = !ret; /* invert */
3315 }
3316
3317 return ret;
3318 }
3319
3320 /* Apply a patch action to a property segment */
apply_patch_property(struct path_segment_t * path_seg,icalcomponent * parent,int * num_changes)3321 static void apply_patch_property(struct path_segment_t *path_seg,
3322 icalcomponent *parent, int *num_changes)
3323 {
3324 icalproperty *prop, *nextprop;
3325 icalparameter *param;
3326
3327 /* Iterate through each property */
3328 for (prop = icalcomponent_get_first_property(parent, path_seg->kind);
3329 prop; prop = nextprop) {
3330 nextprop = icalcomponent_get_next_property(parent, path_seg->kind);
3331
3332 /* Check prop-match */
3333 int match = 1;
3334 if (path_seg->match.prop.param) {
3335 /* Check param-match */
3336 match = apply_param_match(prop, &path_seg->match);
3337 }
3338 else if (path_seg->match.prop.value) {
3339 /* Check prop-[not-]equal */
3340 const char *prop_val = icalproperty_get_value_as_string(prop);
3341
3342 match = !strcmp(path_seg->match.prop.value, prop_val);
3343 if (path_seg->match.prop.not) match = !match; /* invert */
3344 }
3345 if (!match) continue;
3346
3347 if (path_seg->child) {
3348 /* Recurse into next segment */
3349 apply_patch(path_seg->child, prop, num_changes);
3350 }
3351 else if (path_seg->action == ACTION_DELETE) {
3352 /* Delete existing property */
3353 switch (path_seg->kind) {
3354 case ICAL_RDATE_PROPERTY:
3355 case ICAL_EXDATE_PROPERTY:
3356 case ICAL_FREEBUSY_PROPERTY:
3357 case ICAL_CATEGORIES_PROPERTY:
3358 case ICAL_RESOURCES_PROPERTY:
3359 case ICAL_ACCEPTRESPONSE_PROPERTY:
3360 case ICAL_POLLPROPERTIES_PROPERTY:
3361 /* Multi-valued property */
3362 if (path_seg->data) {
3363 /* Check if entire property value == single value */
3364 const char *single = (const char *) path_seg->data;
3365 const char *propval = icalproperty_get_value_as_string(prop);
3366
3367 if (strcmp(propval, single)) {
3368 /* Not an exact match, try to remove single value */
3369 char *newval = remove_single_value(propval, single);
3370 if (newval) {
3371 *num_changes += 1;
3372 icalproperty_set_value(prop,
3373 icalvalue_new_string(newval));
3374 free(newval);
3375 }
3376 break;
3377 }
3378 }
3379
3380 /* Fall through and delete entire property */
3381 GCC_FALLTHROUGH
3382
3383 default:
3384 *num_changes += 1;
3385 icalcomponent_remove_property(parent, prop);
3386 icalproperty_free(prop);
3387 break;
3388 }
3389 }
3390 else if (path_seg->action == ACTION_SETPARAM) {
3391 /* Set parameter(s) from those on PATCH-PARAMETER */
3392 icalproperty *pp_prop = (icalproperty *) path_seg->data;
3393
3394 *num_changes += 1;
3395 for (param = icalproperty_get_first_parameter(pp_prop,
3396 ICAL_ANY_PARAMETER);
3397 param;
3398 param = icalproperty_get_next_parameter(pp_prop,
3399 ICAL_ANY_PARAMETER)) {
3400 icalproperty_set_parameter(prop, icalparameter_new_clone(param));
3401 }
3402 }
3403 }
3404 }
3405
create_override(icalcomponent * master,struct icaltime_span * span,void * rock)3406 static void create_override(icalcomponent *master, struct icaltime_span *span,
3407 void *rock)
3408 {
3409 icalcomponent *new;
3410 icalproperty *prop, *next;
3411 struct icaltimetype dtstart, dtend, now;
3412 const icaltimezone *tz = NULL;
3413 const char *tzid;
3414 int is_date;
3415
3416 now = icaltime_current_time_with_zone(utc_zone);
3417
3418 new = icalcomponent_new_clone(master);
3419
3420 for (prop = icalcomponent_get_first_property(new, ICAL_ANY_PROPERTY);
3421 prop; prop = next) {
3422 next = icalcomponent_get_next_property(new, ICAL_ANY_PROPERTY);
3423
3424 switch (icalproperty_isa(prop)) {
3425 case ICAL_DTSTART_PROPERTY:
3426 /* Set DTSTART for this recurrence */
3427 dtstart = icalproperty_get_dtstart(prop);
3428 is_date = icaltime_is_date(dtstart);
3429 tz = icaltime_get_timezone(dtstart);
3430
3431 dtstart = icaltime_from_timet_with_zone(span->start, is_date, tz);
3432 icaltime_set_timezone(&dtstart, tz);
3433 icalproperty_set_dtstart(prop, dtstart);
3434
3435 /* Add RECURRENCE-ID for this recurrence */
3436 prop = icalproperty_new_recurrenceid(dtstart);
3437 tzid = icaltimezone_get_tzid((icaltimezone *) tz);
3438 if (tzid) {
3439 icalproperty_add_parameter(prop, icalparameter_new_tzid(tzid));
3440 }
3441 icalcomponent_add_property(new, prop);
3442 break;
3443
3444 case ICAL_DTEND_PROPERTY:
3445 /* Set DTEND for this recurrence */
3446 dtend = icalproperty_get_dtend(prop);
3447 is_date = icaltime_is_date(dtend);
3448 tz = icaltime_get_timezone(dtend);
3449
3450 dtend = icaltime_from_timet_with_zone(span->end, is_date, tz);
3451 icaltime_set_timezone(&dtend, tz);
3452 icalproperty_set_dtend(prop, dtend);
3453 break;
3454
3455 case ICAL_RRULE_PROPERTY:
3456 case ICAL_RDATE_PROPERTY:
3457 case ICAL_EXDATE_PROPERTY:
3458 /* Remove recurrence properties */
3459 icalcomponent_remove_property(new, prop);
3460 icalproperty_free(prop);
3461 break;
3462
3463 case ICAL_DTSTAMP_PROPERTY:
3464 /* Update DTSTAMP */
3465 icalproperty_set_dtstamp(prop, now);
3466 break;
3467
3468 case ICAL_CREATED_PROPERTY:
3469 /* Update CREATED */
3470 icalproperty_set_created(prop, now);
3471 break;
3472
3473 case ICAL_LASTMODIFIED_PROPERTY:
3474 /* Update LASTMODIFIED */
3475 icalproperty_set_lastmodified(prop, now);
3476 break;
3477
3478 default:
3479 break;
3480 }
3481 }
3482
3483 *((icalcomponent **) rock) = new;
3484 }
3485
3486 /* Apply property updates */
apply_property_updates(struct patch_data_t * patch,icalcomponent * parent,int * num_changes)3487 static void apply_property_updates(struct patch_data_t *patch,
3488 icalcomponent *parent, int *num_changes)
3489 {
3490 icalproperty *prop = NULL, *nextprop, *newprop;
3491
3492 for (newprop = icalcomponent_get_first_property(patch->patch,
3493 ICAL_ANY_PROPERTY);
3494 newprop;
3495 newprop = icalcomponent_get_next_property(patch->patch,
3496 ICAL_ANY_PROPERTY)) {
3497 icalproperty_kind kind = icalproperty_isa(newprop);
3498 icalparameter_patchaction action = ICAL_PATCHACTION_BYNAME;
3499 icalparameter *actionp;
3500 union match_criteria_t byparam;
3501
3502 memset(&byparam, 0, sizeof(union match_criteria_t));
3503 newprop = icalproperty_new_clone(newprop);
3504
3505 actionp = icalproperty_get_first_parameter(newprop,
3506 ICAL_PATCHACTION_PARAMETER);
3507 if (actionp) {
3508 action = icalparameter_get_patchaction(actionp);
3509 if (action == ICAL_PATCHACTION_X) {
3510 /* libical treats DQUOTEd BYPARAM as X value */
3511 const char *byparam_prefix = "BYPARAM@";
3512 const char *x_val = icalparameter_get_xvalue(actionp);
3513 if (!strncmp(x_val, byparam_prefix, strlen(byparam_prefix))) {
3514 /* Parse param-match */
3515 const char *p = x_val + strlen(byparam_prefix);
3516 size_t namelen = strcspn(p, "!=");
3517 byparam.prop.param = xstrndup(p, namelen);
3518 p += namelen;
3519
3520 if (*p) {
3521 if (*p++ == '!') byparam.prop.not = 1;
3522 byparam.prop.value = xstrdup(p);
3523 }
3524 action = ICAL_PATCHACTION_BYPARAM;
3525 }
3526 }
3527
3528 icalproperty_remove_parameter_by_ref(newprop, actionp);
3529 icalparameter_free(actionp);
3530 }
3531
3532 if (action != ICAL_PATCHACTION_CREATE) {
3533 /* Delete properties matching those being updated */
3534 const char *value = icalproperty_get_value_as_string(newprop);
3535
3536 for (prop = icalcomponent_get_first_property(parent, kind);
3537 prop; prop = nextprop) {
3538 int match = 1;
3539
3540 nextprop = icalcomponent_get_next_property(parent, kind);
3541
3542 if (action == ICAL_PATCHACTION_BYVALUE) {
3543 match = !strcmp(value,
3544 icalproperty_get_value_as_string(prop));
3545 }
3546 else if (action == ICAL_PATCHACTION_BYPARAM) {
3547 /* Check param-match */
3548 match = apply_param_match(prop, &byparam);
3549 free(byparam.prop.param);
3550 free(byparam.prop.value);
3551 }
3552 if (!match) continue;
3553
3554 icalcomponent_remove_property(parent, prop);
3555 icalproperty_free(prop);
3556 }
3557 }
3558
3559 *num_changes += 1;
3560 icalcomponent_add_property(parent, newprop);
3561 }
3562 }
3563
3564 /* Apply property updates */
apply_component_updates(struct patch_data_t * patch,icalcomponent * parent,int * num_changes)3565 static void apply_component_updates(struct patch_data_t *patch,
3566 icalcomponent *parent, int *num_changes)
3567 {
3568 icalcomponent *comp, *nextcomp, *newcomp;
3569
3570 for (newcomp = icalcomponent_get_first_component(patch->patch,
3571 ICAL_ANY_COMPONENT);
3572 newcomp;
3573 newcomp = icalcomponent_get_next_component(patch->patch,
3574 ICAL_ANY_COMPONENT)){
3575 icalcomponent_kind kind = icalcomponent_isa(newcomp);
3576 const char *uid = icalcomponent_get_uid(newcomp);
3577 icaltimetype rid = icalcomponent_get_recurrenceid(newcomp);
3578
3579 newcomp = icalcomponent_new_clone(newcomp);
3580
3581 /* Delete components matching those being updated */
3582 for (comp = icalcomponent_get_first_component(parent, kind);
3583 comp; comp = nextcomp) {
3584
3585 nextcomp = icalcomponent_get_next_component(parent, kind);
3586
3587 if (strcmp(uid, icalcomponent_get_uid(comp)) ||
3588 icaltime_compare(rid, icalcomponent_get_recurrenceid(comp))) {
3589 /* skip */
3590 continue;
3591 }
3592
3593 icalcomponent_remove_component(parent, comp);
3594 icalcomponent_free(comp);
3595 }
3596
3597 *num_changes += 1;
3598 icalcomponent_add_component(parent, newcomp);
3599 }
3600 }
3601
3602 /* Apply a patch action to a component segment */
apply_patch_component(struct path_segment_t * path_seg,icalcomponent * parent,int * num_changes)3603 static void apply_patch_component(struct path_segment_t *path_seg,
3604 icalcomponent *parent, int *num_changes)
3605 {
3606 icalcomponent *comp, *nextcomp, *master = NULL;
3607
3608 /* Iterate through each component */
3609 if (path_seg->kind == ICAL_VCALENDAR_COMPONENT)
3610 comp = parent;
3611 else
3612 comp = icalcomponent_get_first_component(parent, path_seg->kind);
3613
3614 for (; comp; comp = nextcomp) {
3615 nextcomp = icalcomponent_get_next_component(parent, path_seg->kind);
3616
3617 /* Check comp-match */
3618 if (path_seg->match.comp.uid &&
3619 strcmp(path_seg->match.comp.uid, icalcomponent_get_uid(comp))) {
3620 continue; /* UID doesn't match */
3621 }
3622
3623 if (icaltime_is_valid_time(path_seg->match.comp.rid)) {
3624 icaltimetype recurid =
3625 icalcomponent_get_recurrenceid_with_zone(comp);
3626
3627 if (icaltime_is_null_time(recurid)) master = comp;
3628 if (icaltime_compare(recurid, path_seg->match.comp.rid)) {
3629 if (!nextcomp && master) {
3630 /* Possibly add an override recurrence.
3631 Set start and end to coincide with recurrence */
3632 icalcomponent *override = NULL;
3633 struct icaltimetype start = path_seg->match.comp.rid;
3634 struct icaltimetype end =
3635 icaltime_add(start, icalcomponent_get_duration(master));
3636 icalcomponent_foreach_recurrence(master, start, end,
3637 &create_override,
3638 &override);
3639 if (!override) break; /* Can't override - done */
3640
3641 /* Act on new overridden component */
3642 icalcomponent_add_component(parent, override);
3643 comp = override;
3644 }
3645 else continue; /* RECURRENCE-ID doesn't match */
3646 }
3647 }
3648
3649 if (path_seg->child) {
3650 /* Recurse into next segment */
3651 apply_patch(path_seg->child, comp, num_changes);
3652 }
3653 else if (path_seg->action == ACTION_DELETE) {
3654 /* Delete existing component */
3655 *num_changes += 1;
3656 icalcomponent_remove_component(parent, comp);
3657 icalcomponent_free(comp);
3658 }
3659 else if (path_seg->action == ACTION_UPDATE) {
3660 /* Patch existing component */
3661 struct patch_data_t *patch = (struct patch_data_t *) path_seg->data;
3662
3663 /* Process all PATCH-DELETEs first */
3664 for (path_seg = patch->delete;
3665 path_seg; path_seg = path_seg->child) {
3666 apply_patch(path_seg, comp, num_changes);
3667 }
3668
3669 /* Process all PATCH-SETPARAMETERs second */
3670 for (path_seg = patch->setparam;
3671 path_seg; path_seg = path_seg->child) {
3672 apply_patch(path_seg, comp, num_changes);
3673 }
3674
3675 /* Process all components updates third */
3676 apply_component_updates(patch, comp, num_changes);
3677
3678 /* Process all property updates last */
3679 apply_property_updates(patch, comp, num_changes);
3680 }
3681 }
3682 }
3683
3684 /* Apply a patch action to a target segment */
apply_patch(struct path_segment_t * path_seg,void * parent,int * num_changes)3685 static void apply_patch(struct path_segment_t *path_seg,
3686 void *parent, int *num_changes)
3687 {
3688 switch (path_seg->type) {
3689 case SEGMENT_COMP:
3690 apply_patch_component(path_seg, parent, num_changes);
3691 break;
3692
3693 case SEGMENT_PROP:
3694 apply_patch_property(path_seg, parent, num_changes);
3695 break;
3696
3697 case SEGMENT_PARAM:
3698 apply_patch_parameter(path_seg, parent, num_changes);
3699 break;
3700 }
3701 }
3702
path_segment_free(struct path_segment_t * path_seg)3703 static void path_segment_free(struct path_segment_t *path_seg)
3704 {
3705 struct path_segment_t *next;
3706
3707 for (; path_seg; path_seg = next) {
3708 next = path_seg->child;
3709
3710 switch (path_seg->type) {
3711 case SEGMENT_COMP:
3712 free(path_seg->match.comp.uid);
3713 break;
3714
3715 case SEGMENT_PROP:
3716 free(path_seg->match.prop.param);
3717 free(path_seg->match.prop.value);
3718 break;
3719
3720 case SEGMENT_PARAM:
3721 break;
3722 }
3723
3724 free(path_seg);
3725 }
3726 }
3727
3728
3729 /* Perform a PATCH request
3730 *
3731 * preconditions:
3732 */
caldav_patch(struct transaction_t * txn,void * obj)3733 static int caldav_patch(struct transaction_t *txn, void *obj)
3734 {
3735 icalcomponent *ical = (icalcomponent *) obj;
3736 icalcomponent *pdoc, *vpatch, *patch;
3737 icalproperty *prop;
3738 int num_changes = 0;
3739 int ret = 0;
3740
3741 /* Validate the iCal patch */
3742 pdoc = ical_string_as_icalcomponent(&txn->req_body.payload);
3743 if (!pdoc || (icalcomponent_isa(pdoc) != ICAL_VCALENDAR_COMPONENT)) {
3744 txn->error.desc = "Missing VCALENDAR";
3745 txn->error.precond = CALDAV_VALID_DATA;
3746 ret = HTTP_BAD_REQUEST;
3747 }
3748 else if (!icalrestriction_check(pdoc) || icalcomponent_count_errors(pdoc)) {
3749 if ((txn->error.desc = get_icalcomponent_errstr(pdoc)) ||
3750 (txn->error.desc =
3751 get_icalcomponent_errstr(icalcomponent_get_first_real_component(pdoc)))) {
3752 buf_setcstr(&txn->buf, txn->error.desc);
3753 txn->error.desc = buf_cstring(&txn->buf);
3754 }
3755 txn->error.precond = CALDAV_VALID_DATA;
3756 ret = HTTP_BAD_REQUEST;
3757 }
3758 else if (!(vpatch = icalcomponent_get_first_real_component(pdoc)) ||
3759 icalcomponent_isa(vpatch) != ICAL_VPATCH_COMPONENT) {
3760 txn->error.desc = "Missing VPATCH";
3761 txn->error.precond = CALDAV_VALID_DATA;
3762 ret = HTTP_BAD_REQUEST;
3763 }
3764 else if ((prop =
3765 icalcomponent_get_first_property(vpatch,
3766 ICAL_PATCHVERSION_PROPERTY)) &&
3767 strcmp(icalproperty_get_patchversion(prop), "1")) {
3768 txn->error.desc = "Unsupported PATCH-VERSION";
3769 txn->error.precond = CALDAV_SUPP_DATA;
3770 ret = HTTP_BAD_REQUEST;
3771 }
3772
3773 if (ret) goto done;
3774
3775 /* Process each patch sub-component */
3776 for (patch = icalcomponent_get_first_component(vpatch, ICAL_ANY_COMPONENT);
3777 patch;
3778 patch = icalcomponent_get_next_component(vpatch, ICAL_ANY_COMPONENT)) {
3779
3780 if (icalcomponent_isa(patch) != ICAL_XPATCH_COMPONENT) {
3781 /* Unknown patch action */
3782 txn->error.precond = CALDAV_SUPP_COMP;
3783 ret = HTTP_BAD_REQUEST;
3784 goto done;
3785 }
3786
3787 prop = icalcomponent_get_first_property(patch,
3788 ICAL_PATCHTARGET_PROPERTY);
3789 if (!prop) {
3790 txn->error.desc = "Missing TARGET";
3791 txn->error.precond = CALDAV_VALID_DATA;
3792 ret = HTTP_BAD_REQUEST;
3793 goto done;
3794 }
3795
3796 /* Parse PATCH-TARGET */
3797 char *path = xstrdup(icalproperty_get_patchtarget(prop));
3798 struct path_segment_t *target = NULL, *next;
3799 struct patch_data_t patch_data = { patch, NULL, NULL };
3800
3801 icalcomponent_remove_property(patch, prop);
3802 icalproperty_free(prop);
3803
3804 ret = parse_target_path(path, &target,
3805 ACTION_UPDATE, &patch_data, &txn->error);
3806 free(path);
3807 if (!ret) {
3808 if (!target || target->type != SEGMENT_COMP ||
3809 target->kind != ICAL_VCALENDAR_COMPONENT ||
3810 target->match.comp.uid) {
3811 txn->error.desc = "Initial segment of PATCH-TARGET"
3812 " MUST be an unmatched VCALENDAR";
3813 ret = HTTP_BAD_REQUEST;
3814 }
3815 }
3816
3817 if (!ret) {
3818 /* Parse and remove all PATCH-DELETEs and PATCH-PARAMETERs */
3819 icalproperty *nextprop;
3820 for (prop =
3821 icalcomponent_get_first_property(patch, ICAL_ANY_PROPERTY);
3822 !ret && prop; prop = nextprop) {
3823
3824 icalproperty_kind kind = icalproperty_isa(prop);
3825 struct path_segment_t *ppath = NULL;
3826
3827 nextprop =
3828 icalcomponent_get_next_property(patch, ICAL_ANY_PROPERTY);
3829
3830 if (kind == ICAL_PATCHDELETE_PROPERTY) {
3831 path = xstrdup(icalproperty_get_patchdelete(prop));
3832
3833 icalcomponent_remove_property(patch, prop);
3834 icalproperty_free(prop);
3835
3836 ret = parse_target_path(path, &ppath,
3837 ACTION_DELETE, NULL, &txn->error);
3838 free(path);
3839 if (!ret) {
3840 if (!ppath ||
3841 (ppath->type == SEGMENT_COMP &&
3842 ppath->kind == ICAL_VCALENDAR_COMPONENT)) {
3843 txn->error.desc = "Initial segment of PATCH-DELETE"
3844 " MUST NOT be VCALENDAR";
3845 ret = HTTP_BAD_REQUEST;
3846 }
3847 else {
3848 /* Add this delete path to our list */
3849 ppath->sibling = patch_data.delete;
3850 patch_data.delete = ppath;
3851 }
3852 }
3853 }
3854 else if (kind == ICAL_PATCHPARAMETER_PROPERTY) {
3855 path = xstrdup(icalproperty_get_patchparameter(prop));
3856
3857 icalcomponent_remove_property(patch, prop);
3858
3859 ret = parse_target_path(path, &ppath,
3860 ACTION_SETPARAM, prop, &txn->error);
3861 free(path);
3862 if (!ret) {
3863 if (!ppath || ppath->type != SEGMENT_PROP) {
3864 txn->error.desc =
3865 "Initial segment of PATCH-PARAMETER"
3866 " MUST be a property";
3867 ret = HTTP_BAD_REQUEST;
3868 }
3869 else {
3870 /* Add this setparam path to our list */
3871 ppath->sibling = patch_data.setparam;
3872 patch_data.setparam = ppath;
3873 }
3874 }
3875 }
3876 }
3877 }
3878
3879 /* Apply this patch to the target component */
3880 if (!ret) apply_patch(target, ical, &num_changes);
3881
3882 /* Cleanup target paths */
3883 path_segment_free(target);
3884 for (target = patch_data.delete; target; target = next) {
3885 next = target->sibling;
3886 if (target->data) free(target->data);
3887 path_segment_free(target);
3888 }
3889 for (target = patch_data.setparam; target; target = next) {
3890 next = target->sibling;
3891 if (target->data) icalproperty_free(target->data);
3892 path_segment_free(target);
3893 }
3894
3895 if (ret) goto done;
3896 }
3897
3898 done:
3899 icalcomponent_free(pdoc);
3900
3901 if (ret) return ret;
3902
3903 /* If no changes are made,
3904 return HTTP_NO_CONTENT to suppress storing of resource */
3905 return (!num_changes ? HTTP_NO_CONTENT : 0);
3906 }
3907 #else
caldav_patch(struct transaction_t * txn,void * obj)3908 static int caldav_patch(struct transaction_t *txn __attribute__((unused)),
3909 void *obj __attribute__((unused)))
3910
3911 {
3912 fatal("caldav_patch() called, but no VPATCH", EC_SOFTWARE);
3913 }
3914 #endif /* HAVE_VPATCH */
3915
3916
3917 /* Perform a PUT request
3918 *
3919 * preconditions:
3920 * CALDAV:valid-calendar-data
3921 * CALDAV:valid-calendar-object-resource
3922 * CALDAV:supported-calendar-component
3923 * CALDAV:no-uid-conflict (DAV:href)
3924 * CALDAV:max-resource-size
3925 * CALDAV:min-date-time
3926 * CALDAV:max-date-time
3927 * CALDAV:max-instances
3928 * CALDAV:max-attendees-per-instance
3929 */
caldav_put(struct transaction_t * txn,void * obj,struct mailbox * mailbox,const char * resource,void * destdb,unsigned flags)3930 static int caldav_put(struct transaction_t *txn, void *obj,
3931 struct mailbox *mailbox, const char *resource,
3932 void *destdb, unsigned flags)
3933 {
3934 int ret = 0;
3935 struct caldav_db *db = (struct caldav_db *)destdb;
3936 icalcomponent *ical = (icalcomponent *)obj;
3937 icalcomponent *oldical = NULL;
3938 icalcomponent *comp, *nextcomp;
3939 icalcomponent_kind kind;
3940 icalproperty *prop, *rrule = NULL;
3941 const char *uid, *organizer = NULL;
3942 char *schedule_address = NULL;
3943 struct buf buf = BUF_INITIALIZER;
3944 struct caldav_data *cdata;
3945
3946 /* Validate the iCal data */
3947 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
3948 txn->error.precond = CALDAV_VALID_DATA;
3949 ret = HTTP_FORBIDDEN;
3950 goto done;
3951 }
3952
3953 icalrestriction_check(ical);
3954 if ((txn->error.desc = get_icalcomponent_errstr(ical))) {
3955 buf_setcstr(&txn->buf, txn->error.desc);
3956 txn->error.desc = buf_cstring(&txn->buf);
3957 txn->error.precond = CALDAV_VALID_DATA;
3958 ret = HTTP_FORBIDDEN;
3959 goto done;
3960 }
3961
3962 comp = icalcomponent_get_first_real_component(ical);
3963 if (rscale_calendars) {
3964 /* Grab RRULE to check RSCALE */
3965 rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
3966 }
3967
3968 /* Make sure iCal UIDs [and ORGANIZERs] in all components are the same */
3969 kind = icalcomponent_isa(comp);
3970 uid = icalcomponent_get_uid(comp);
3971 if (!uid) {
3972 txn->error.precond = CALDAV_VALID_OBJECT;
3973 ret = HTTP_FORBIDDEN;
3974 goto done;
3975 }
3976 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
3977 if (prop) organizer = icalproperty_get_organizer(prop);
3978 while ((nextcomp =
3979 icalcomponent_get_next_component(ical, kind))) {
3980 const char *nextuid = icalcomponent_get_uid(nextcomp);
3981
3982 if (!nextuid || strcmp(uid, nextuid)) {
3983 txn->error.precond = CALDAV_VALID_OBJECT;
3984 ret = HTTP_FORBIDDEN;
3985 goto done;
3986 }
3987
3988 const char *nextorg = NULL;
3989
3990 prop = icalcomponent_get_first_property(nextcomp,
3991 ICAL_ORGANIZER_PROPERTY);
3992 if (prop) nextorg = icalproperty_get_organizer(prop);
3993 /* if no toplevel organizer, use the one from here */
3994 if (!organizer && nextorg) organizer = nextorg;
3995 if (nextorg && strcmp(organizer, nextorg)) {
3996 txn->error.precond = CALDAV_SAME_ORGANIZER;
3997 ret = HTTP_FORBIDDEN;
3998 goto done;
3999 }
4000
4001 if (rscale_calendars && !rrule) {
4002 /* Grab RRULE to check RSCALE */
4003 rrule = icalcomponent_get_first_property(nextcomp,
4004 ICAL_RRULE_PROPERTY);
4005 }
4006 }
4007
4008 #ifdef HAVE_RSCALE
4009 /* Make sure we support the provided RSCALE in an RRULE */
4010 if (rrule && rscale_calendars) {
4011 struct icalrecurrencetype rt = icalproperty_get_rrule(rrule);
4012
4013 if (rt.rscale && *rt.rscale) {
4014 /* Perform binary search on sorted icalarray */
4015 unsigned found = 0, start = 0, end = rscale_calendars->num_elements;
4016
4017 ucase((char *) rt.rscale);
4018 while (!found && start < end) {
4019 unsigned mid = start + (end - start) / 2;
4020 const char **rscale =
4021 icalarray_element_at(rscale_calendars, mid);
4022 int r = strcmp(rt.rscale, *rscale);
4023
4024 if (r == 0) found = 1;
4025 else if (r < 0) end = mid;
4026 else start = mid + 1;
4027 }
4028
4029 if (!found) {
4030 txn->error.precond = CALDAV_SUPP_RSCALE;
4031 ret = HTTP_FORBIDDEN;
4032 goto done;
4033 }
4034 }
4035 }
4036 #endif /* HAVE_RSCALE */
4037
4038 /* Check for changed UID */
4039 caldav_lookup_resource(db, mailbox->name, resource, &cdata, 0);
4040 if (cdata->dav.imap_uid && strcmpsafe(cdata->ical_uid, uid)) {
4041 ret = HTTP_FORBIDDEN;
4042 }
4043 else {
4044 /* Check for duplicate iCalendar UID */
4045 caldav_lookup_uid(db, uid, &cdata);
4046 if (cdata->dav.imap_uid && (strcmp(cdata->dav.mailbox, mailbox->name) ||
4047 strcmp(cdata->dav.resource, resource))) {
4048 ret = HTTP_FORBIDDEN;
4049 }
4050 }
4051 if (ret) {
4052 /* CALDAV:no-uid-conflict */
4053 char *owner = mboxname_to_userid(cdata->dav.mailbox);
4054
4055 txn->error.precond = CALDAV_UID_CONFLICT;
4056 buf_reset(&txn->buf);
4057 buf_printf(&txn->buf, "%s/%s/%s/%s/%s",
4058 namespace_calendar.prefix, USER_COLLECTION_PREFIX, owner,
4059 strrchr(cdata->dav.mailbox, '.')+1, cdata->dav.resource);
4060 txn->error.resource = buf_cstring(&txn->buf);
4061 free(owner);
4062 ret = HTTP_FORBIDDEN;
4063 goto done;
4064 }
4065
4066 if (namespace_calendar.allow & ALLOW_CAL_ATTACH) {
4067 ret = manage_attachments(txn, mailbox, ical,
4068 cdata, &oldical, &schedule_address);
4069 if (ret) goto done;
4070 }
4071
4072 switch (kind) {
4073 case ICAL_VEVENT_COMPONENT:
4074 case ICAL_VTODO_COMPONENT:
4075 case ICAL_VPOLL_COMPONENT:
4076 if (organizer) {
4077 /* Scheduling object resource */
4078 strarray_t schedule_addresses = STRARRAY_INITIALIZER;
4079 int r;
4080
4081 syslog(LOG_DEBUG,
4082 "caldav_put: organizer: %s", organizer);
4083
4084 if (!strncasecmp(organizer, "mailto:", 7)) organizer += 7;
4085
4086 if (cdata->organizer && !spool_getheader(txn->req_hdrs, "Allow-Organizer-Change")) {
4087 /* Don't allow ORGANIZER to be changed */
4088 if (strcmp(cdata->organizer, organizer)) {
4089 txn->error.desc = "Can not change organizer address";
4090 ret = HTTP_FORBIDDEN;
4091 }
4092 }
4093
4094 /* existing record? */
4095 if (cdata->dav.imap_uid && !oldical) {
4096 /* Update existing object */
4097 struct index_record record;
4098
4099 syslog(LOG_NOTICE, "LOADING ICAL %u", cdata->dav.imap_uid);
4100
4101 /* Load message containing the resource and parse iCal data */
4102 r = mailbox_find_index_record(mailbox,
4103 cdata->dav.imap_uid, &record);
4104 if (r) {
4105 txn->error.desc = "Failed to read record \r\n";
4106 ret = HTTP_SERVER_ERROR;
4107 goto done;
4108 }
4109
4110 oldical = record_to_ical(mailbox, &record, &schedule_address);
4111 }
4112
4113 if (schedule_address) {
4114 strarray_appendm(&schedule_addresses, schedule_address);
4115 schedule_address = NULL;
4116 }
4117 get_schedule_addresses(txn, &schedule_addresses);
4118
4119 char *userid = mboxname_to_userid(txn->req_tgt.mbentry->name);
4120 if (strarray_find_case(&schedule_addresses, organizer, 0) >= 0) {
4121 /* Organizer scheduling object resource */
4122 if (ret) {
4123 txn->error.precond = CALDAV_ALLOWED_ORG_CHANGE;
4124 }
4125 else {
4126 schedule_address = xstrdupnull(organizer);
4127 if (_scheduling_enabled(txn, mailbox))
4128 sched_request(userid, schedule_address, oldical, ical);
4129 }
4130 }
4131 else {
4132 /* Attendee scheduling object resource */
4133 if (ret) {
4134 txn->error.precond = CALDAV_ALLOWED_ATT_CHANGE;
4135 }
4136 #if 0
4137 else if (!oldical) {
4138 /* Can't reply to a non-existent invitation */
4139 /* XXX But what about invites over iMIP? */
4140 txn->error.desc = "Can not reply to non-existent resource";
4141 ret = HTTP_FORBIDDEN;
4142 }
4143 #endif
4144 else {
4145 schedule_address = xstrdupnull(strarray_nth(&schedule_addresses, 0));
4146 if (_scheduling_enabled(txn, mailbox) && schedule_address)
4147 sched_reply(userid, schedule_address, oldical, ical);
4148 }
4149 }
4150 free(userid);
4151 strarray_fini(&schedule_addresses);
4152
4153 if (ret) goto done;
4154
4155 flags |= NEW_STAG;
4156 }
4157 break;
4158
4159 case ICAL_VJOURNAL_COMPONENT:
4160 case ICAL_VFREEBUSY_COMPONENT:
4161 case ICAL_VAVAILABILITY_COMPONENT:
4162 /* Nothing else to do */
4163 break;
4164
4165 default:
4166 txn->error.precond = CALDAV_SUPP_COMP;
4167 ret = HTTP_FORBIDDEN;
4168 goto done;
4169 }
4170
4171 /* Store resource at target */
4172 if (!ret) {
4173 ret = caldav_store_resource(txn, ical, mailbox,
4174 resource, db, flags, schedule_address);
4175 }
4176
4177 done:
4178 if (oldical) icalcomponent_free(oldical);
4179 free(schedule_address);
4180 buf_free(&buf);
4181
4182 return ret;
4183 }
4184
4185
4186 struct comp_filter {
4187 unsigned comp_type;
4188 xmlChar *name; /* need this for X- components */
4189 unsigned depth;
4190 icalcomponent_kind kind;
4191 unsigned allof : 1;
4192 unsigned not_defined : 1;
4193 struct icalperiodtype *range;
4194 struct prop_filter *prop;
4195 struct comp_filter *comp;
4196 struct comp_filter *next;
4197 };
4198
4199 struct calquery_filter {
4200 unsigned flags; /* mask of flags controlling filter */
4201 unsigned comp_types; /* mask of "real" component types in filter */
4202 icaltimezone *tz;
4203 struct comp_filter *comp;
4204 };
4205
4206 /* Bitmask of calquery flags */
4207 enum {
4208 PARSE_ICAL = (1<<0)
4209 };
4210
is_valid_timerange(const struct icaltimetype start,const struct icaltimetype end)4211 static int is_valid_timerange(const struct icaltimetype start,
4212 const struct icaltimetype end)
4213 {
4214 return (icaltime_is_valid_time(start) && icaltime_is_valid_time(end) &&
4215 !icaltime_is_date(start) && !icaltime_is_date(end) &&
4216 (icaltime_is_utc(start) || start.zone) &&
4217 (icaltime_is_utc(end) || end.zone));
4218 }
4219
parse_timerange(xmlNodePtr node,struct icalperiodtype ** range,struct error_t * error)4220 static void parse_timerange(xmlNodePtr node,
4221 struct icalperiodtype **range, struct error_t *error)
4222 {
4223 xmlChar *attr;
4224 int count = 0;
4225
4226 *range = xzmalloc(sizeof(struct icalperiodtype));
4227
4228 attr = xmlGetProp(node, BAD_CAST "start");
4229 if (attr) {
4230 count++;
4231 (*range)->start = icaltime_from_string((char *) attr);
4232 xmlFree(attr);
4233 }
4234 else {
4235 (*range)->start =
4236 icaltime_from_timet_with_zone(caldav_epoch, 0, utc_zone);
4237 }
4238
4239 attr = xmlGetProp(node, BAD_CAST "end");
4240 if (attr) {
4241 count++;
4242 (*range)->end = icaltime_from_string((char *) attr);
4243 xmlFree(attr);
4244 }
4245 else {
4246 (*range)->end =
4247 icaltime_from_timet_with_zone(caldav_eternity, 0, utc_zone);
4248 }
4249
4250 if (!count || !is_valid_timerange((*range)->start, (*range)->end)) {
4251 error->precond = CALDAV_VALID_FILTER;
4252 error->desc = "Invalid time-range";
4253 error->node = xmlCopyNode(node->parent, 1);
4254 }
4255 }
4256
cal_parse_propfilter(xmlNodePtr node,struct prop_filter * prop,struct error_t * error)4257 static void cal_parse_propfilter(xmlNodePtr node, struct prop_filter *prop,
4258 struct error_t *error)
4259 {
4260 if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
4261 if (prop->other) {
4262 error->precond = CALDAV_SUPP_FILTER;
4263 error->desc = "Multiple time-range";
4264 error->node = xmlCopyNode(node->parent, 1);
4265 }
4266 else {
4267 struct icalperiodtype *range = NULL;
4268 icalvalue_kind kind =
4269 icalproperty_kind_to_value_kind(prop->kind);
4270
4271 switch (kind) {
4272 case ICAL_DATE_VALUE:
4273 case ICAL_DATETIME_VALUE:
4274 case ICAL_DATETIMEPERIOD_VALUE:
4275 case ICAL_PERIOD_VALUE:
4276 parse_timerange(node, &range, error);
4277 prop->other = range;
4278 break;
4279
4280 default:
4281 error->precond = CALDAV_SUPP_FILTER;
4282 error->desc = "Property does not support time-range";
4283 error->node = xmlCopyNode(node->parent, 1);
4284 break;
4285 }
4286 }
4287 }
4288 else {
4289 error->precond = CALDAV_SUPP_FILTER;
4290 error->desc = "Unsupported element in prop-filter";
4291 error->node = xmlCopyNode(node->parent, 1);
4292 }
4293 }
4294
4295 /* This handles calendar-query-extended per draft-daboo-caldav-extensions */
parse_compfilter(xmlNodePtr root,unsigned depth,struct comp_filter ** comp,unsigned * flags,unsigned * comp_types,struct error_t * error)4296 static void parse_compfilter(xmlNodePtr root, unsigned depth,
4297 struct comp_filter **comp, unsigned *flags,
4298 unsigned *comp_types, struct error_t *error)
4299 {
4300 xmlChar *attr;
4301 xmlNodePtr node;
4302 struct filter_profile_t profile =
4303 { 1 /* allof */, COLLATION_ASCII,
4304 CALDAV_SUPP_FILTER, CALDAV_SUPP_COLLATION,
4305 &icalproperty_string_to_kind, ICAL_NO_PROPERTY,
4306 &icalparameter_string_to_kind, ICAL_NO_PARAMETER,
4307 &cal_parse_propfilter };
4308
4309 /* Parse elements of comp-filter */
4310 attr = xmlGetProp(root, BAD_CAST "name");
4311 if (!attr) {
4312 error->precond = CALDAV_SUPP_FILTER;
4313 error->desc = "Missing 'name' attribute";
4314 error->node = xmlCopyNode(root, 2);
4315 }
4316 else {
4317 icalcomponent_kind kind;
4318
4319 if (!xmlStrcmp(attr, BAD_CAST "*")) kind = ICAL_ANY_COMPONENT;
4320 else kind = icalcomponent_string_to_kind((const char *) attr);
4321
4322 *comp = xzmalloc(sizeof(struct comp_filter));
4323 (*comp)->name = attr;
4324 (*comp)->depth = depth;
4325 (*comp)->kind = kind;
4326 (*comp)->allof = 1;
4327
4328 if (kind == ICAL_NO_COMPONENT) {
4329 error->precond = CALDAV_SUPP_FILTER;
4330 error->desc = "Unsupported component";
4331 error->node = xmlCopyNode(root, 2);
4332 }
4333 else {
4334 switch (depth) {
4335 case 0:
4336 /* VCALENDAR */
4337 if (kind != ICAL_VCALENDAR_COMPONENT) {
4338 /* All other components MUST be a decendent of VCALENDAR */
4339 error->precond = CALDAV_VALID_FILTER;
4340 error->desc = "VCALENDAR must be toplevel component";
4341 }
4342 break;
4343
4344 case 1:
4345 /* Child of VCALENDAR */
4346 switch (kind) {
4347 case ICAL_VCALENDAR_COMPONENT:
4348 /* VCALENDAR MUST only appear at toplevel */
4349 error->precond = CALDAV_VALID_FILTER;
4350 error->desc = "VCALENDAR can only be toplevel component";
4351 break;
4352 case ICAL_VEVENT_COMPONENT:
4353 (*comp)->comp_type = CAL_COMP_VEVENT;
4354 break;
4355 case ICAL_VTODO_COMPONENT:
4356 (*comp)->comp_type = CAL_COMP_VTODO;
4357 break;
4358 case ICAL_VJOURNAL_COMPONENT:
4359 (*comp)->comp_type = CAL_COMP_VJOURNAL;
4360 break;
4361 case ICAL_VFREEBUSY_COMPONENT:
4362 (*comp)->comp_type = CAL_COMP_VFREEBUSY;
4363 break;
4364 case ICAL_VAVAILABILITY_COMPONENT:
4365 (*comp)->comp_type = CAL_COMP_VAVAILABILITY;
4366 break;
4367 case ICAL_VPOLL_COMPONENT:
4368 (*comp)->comp_type = CAL_COMP_VPOLL;
4369 break;
4370 default:
4371 *flags |= PARSE_ICAL;
4372 break;
4373 }
4374
4375 *comp_types |= (*comp)->comp_type;
4376 break;
4377
4378 default:
4379 /* [Great*] grandchild of VCALENDAR */
4380 if (kind == ICAL_VCALENDAR_COMPONENT) {
4381 /* VCALENDAR MUST only appear at toplevel */
4382 error->precond = CALDAV_VALID_FILTER;
4383 error->desc = "VCALENDAR can only be toplevel component";
4384 }
4385 else *flags |= PARSE_ICAL;
4386 break;
4387 }
4388 }
4389
4390 if (!error->precond) {
4391 attr = xmlGetProp(root, BAD_CAST "test");
4392 if (attr) {
4393 if (!xmlStrcmp(attr, BAD_CAST "anyof")) (*comp)->allof = 0;
4394 else if (xmlStrcmp(attr, BAD_CAST "allof")) {
4395 error->precond = CALDAV_SUPP_FILTER;
4396 error->desc = "Unsupported test";
4397 error->node = xmlCopyNode(root, 2);
4398 }
4399 xmlFree(attr);
4400 }
4401 }
4402 }
4403
4404 for (node = xmlFirstElementChild(root); node && !error->precond;
4405 node = xmlNextElementSibling(node)) {
4406
4407 if ((*comp)->not_defined) {
4408 error->precond = CALDAV_SUPP_FILTER;
4409 error->desc = DAV_FILTER_ISNOTDEF_ERR;
4410 error->node = xmlCopyNode(root, 1);
4411 }
4412 else if (!xmlStrcmp(node->name, BAD_CAST "is-not-defined")) {
4413 if ((*comp)->range || (*comp)->prop || (*comp)->comp) {
4414 error->precond = CALDAV_SUPP_FILTER;
4415 error->desc = DAV_FILTER_ISNOTDEF_ERR;
4416 error->node = xmlCopyNode(root, 1);
4417 }
4418 else {
4419 *flags |= PARSE_ICAL;
4420 (*comp)->not_defined = 1;
4421 if ((*comp)->comp_type) {
4422 *comp_types &= ~(*comp)->comp_type;
4423 (*comp)->comp_type = 0;
4424 }
4425 }
4426 }
4427 else if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
4428 if ((*comp)->range) {
4429 error->precond = CALDAV_SUPP_FILTER;
4430 error->desc = "Multiple time-range";
4431 error->node = xmlCopyNode(root, 1);
4432 }
4433 else {
4434 switch ((*comp)->kind) {
4435 case ICAL_ANY_COMPONENT:
4436 case ICAL_VEVENT_COMPONENT:
4437 case ICAL_VTODO_COMPONENT:
4438 case ICAL_VJOURNAL_COMPONENT:
4439 case ICAL_VFREEBUSY_COMPONENT:
4440 case ICAL_VAVAILABILITY_COMPONENT:
4441 case ICAL_VPOLL_COMPONENT:
4442 parse_timerange(node, &(*comp)->range, error);
4443 break;
4444
4445 default:
4446 error->precond = CALDAV_SUPP_FILTER;
4447 error->desc = "time-range unsupported for this component";
4448 error->node = xmlCopyNode(root, 1);
4449 break;
4450 }
4451 }
4452
4453 if (depth != 1) *flags |= PARSE_ICAL;
4454 }
4455 else if (!xmlStrcmp(node->name, BAD_CAST "prop-filter")) {
4456 struct prop_filter *prop = NULL;
4457
4458 *flags |= PARSE_ICAL;
4459
4460 dav_parse_propfilter(node, &prop, &profile, error);
4461 if (prop) {
4462 if ((*comp)->prop) prop->next = (*comp)->prop;
4463 (*comp)->prop = prop;
4464 if (prop->match) {
4465 if (prop->other || prop->match->next) {
4466 error->precond = CALDAV_SUPP_FILTER;
4467 error->desc = prop->match->next ? "Multiple text-match" :
4468 "time-range can NOT be combined with text-match";
4469 error->node = xmlCopyNode(node, 1);
4470 }
4471 }
4472 }
4473 }
4474 else if (!xmlStrcmp(node->name, BAD_CAST "comp-filter")) {
4475 struct comp_filter *subcomp = NULL;
4476
4477 parse_compfilter(node, depth + 1, &subcomp,
4478 flags, comp_types, error);
4479 if (subcomp) {
4480 if ((*comp)->comp) subcomp->next = (*comp)->comp;
4481 (*comp)->comp = subcomp;
4482 }
4483 }
4484 else {
4485 error->precond = CALDAV_SUPP_FILTER;
4486 error->desc = "Unsupported element in comp-filter";
4487 error->node = xmlCopyNode(root, 1);
4488 }
4489 }
4490 }
4491
parse_calfilter(xmlNodePtr root,struct calquery_filter * filter,struct error_t * error)4492 static int parse_calfilter(xmlNodePtr root, struct calquery_filter *filter,
4493 struct error_t *error)
4494 {
4495 xmlNodePtr node;
4496
4497 /* Parse elements of filter */
4498 node = xmlFirstElementChild(root);
4499 if (node && !xmlStrcmp(node->name, BAD_CAST "comp-filter")) {
4500 parse_compfilter(node, 0, &filter->comp,
4501 &filter->flags, &filter->comp_types, error);
4502 }
4503 else {
4504 error->precond = CALDAV_VALID_FILTER;
4505 error->desc = "missing comp-filter element";
4506 }
4507
4508 return error->precond ? HTTP_FORBIDDEN : 0;
4509 }
4510
4511
apply_paramfilter(struct param_filter * paramfilter,icalproperty * prop)4512 static int apply_paramfilter(struct param_filter *paramfilter,
4513 icalproperty *prop)
4514 {
4515 int pass = 1;
4516 icalparameter *param =
4517 icalproperty_get_first_parameter(prop, paramfilter->kind);
4518
4519 if (paramfilter->kind == ICAL_X_PARAMETER) {
4520 /* Find the first X- parameter with matching name */
4521 for (; param && strcmp((const char *) paramfilter->name,
4522 icalparameter_get_xname(param));
4523 param = icalproperty_get_next_parameter(prop, paramfilter->kind));
4524 }
4525
4526 if (!param) return paramfilter->not_defined;
4527 if (paramfilter->not_defined) return 0;
4528 if (!paramfilter->match) return 1;
4529
4530 /* Test each instance of this parameter (logical OR) */
4531 do {
4532 const char *text;
4533
4534 if (!pass && (paramfilter->kind == ICAL_X_PARAMETER) &&
4535 strcmp((const char *) paramfilter->name,
4536 icalparameter_get_xname(param))) {
4537 /* Skip X- parameter if name doesn't match */
4538 continue;
4539 }
4540
4541 text = icalparameter_get_iana_value(param);
4542 pass = dav_apply_textmatch(BAD_CAST text, paramfilter->match);
4543
4544 } while (!pass &&
4545 (param = icalproperty_get_next_parameter(prop, paramfilter->kind)));
4546
4547 return pass;
4548 }
4549
apply_prop_timerange(struct icalperiodtype * range,icalproperty * prop)4550 static int apply_prop_timerange(struct icalperiodtype *range, icalproperty *prop)
4551 {
4552 icalvalue *value = icalproperty_get_value(prop);
4553 struct icalperiodtype period = icalperiodtype_null_period();
4554 icalparameter *param;
4555
4556 switch (icalvalue_isa(value)) {
4557 case ICAL_DATE_VALUE:
4558 period.start = icalvalue_get_date(value);
4559 period.start.is_date = 0; /* MUST be DATE-TIME */
4560 break;
4561
4562 case ICAL_DATETIME_VALUE:
4563 period.start = icalvalue_get_datetime(value);
4564 break;
4565
4566 case ICAL_PERIOD_VALUE:
4567 period = icalvalue_get_period(value);
4568 break;
4569
4570 default:
4571 /* Should never get here */
4572 break;
4573 }
4574
4575 /* Set the timezone, if any, on the start time */
4576 if ((param = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER))) {
4577 const char *tzid = icalparameter_get_tzid(param);
4578 icaltimezone *tz = NULL;
4579 icalcomponent *comp;
4580
4581 for (comp = icalproperty_get_parent(prop); comp;
4582 comp = icalcomponent_get_parent(comp)) {
4583 tz = icalcomponent_get_timezone(comp, tzid);
4584 if (tz) break;
4585 }
4586
4587 if (!tz) tz = icaltimezone_get_builtin_timezone_from_tzid(tzid);
4588
4589 if (tz) period.start = icaltime_set_timezone(&period.start, tz);
4590 }
4591
4592 if (!icaldurationtype_is_null_duration(period.duration)) {
4593 /* Calculate end time from duration */
4594 period.end = icaltime_add(period.start, period.duration);
4595 }
4596 else if (icaltime_is_null_time(period.end)) period.end = period.start;
4597
4598 /* Convert to UTC for comparison with range */
4599 period.start = icaltime_convert_to_zone(period.start, utc_zone);
4600 period.end = icaltime_convert_to_zone(period.end, utc_zone);
4601
4602 if (icaltime_compare(period.start, range->end) >= 0 ||
4603 icaltime_compare(period.end, range->start) <= 0) {
4604 /* Starts later or ends earlier than range */
4605 return 0;
4606 }
4607
4608 return 1;
4609 }
4610
apply_propfilter(struct prop_filter * propfilter,icalcomponent * comp)4611 static int apply_propfilter(struct prop_filter *propfilter, icalcomponent *comp)
4612 {
4613 int pass = 1;
4614 icalproperty *prop =
4615 icalcomponent_get_first_property(comp, propfilter->kind);
4616
4617 if (propfilter->kind == ICAL_X_PROPERTY) {
4618 /* Find the first X- property with matching name */
4619 for (; prop && strcmp((const char *) propfilter->name,
4620 icalproperty_get_property_name(prop));
4621 prop = icalcomponent_get_next_property(comp, propfilter->kind));
4622 }
4623
4624 if (!prop) return propfilter->not_defined;
4625 if (propfilter->not_defined) return 0;
4626 if (!(propfilter->other || propfilter->match || propfilter->param)) return 1;
4627
4628 /* Test each instance of this property (logical OR) */
4629 do {
4630 struct param_filter *paramfilter;
4631
4632 if (!pass && (propfilter->kind == ICAL_X_PROPERTY) &&
4633 strcmp((const char *) propfilter->name,
4634 icalproperty_get_property_name(prop))) {
4635 /* Skip X- property if name doesn't match */
4636 continue;
4637 }
4638
4639 pass = propfilter->allof;
4640
4641 if (propfilter->other) {
4642 pass = apply_prop_timerange(propfilter->other, prop);
4643 }
4644 else if (propfilter->match) {
4645 const char *text = icalproperty_get_value_as_string(prop);
4646
4647 pass = dav_apply_textmatch(BAD_CAST text, propfilter->match);
4648 }
4649
4650 /* Apply each param-filter, breaking if allof fails or anyof succeeds */
4651 for (paramfilter = propfilter->param;
4652 paramfilter && (pass == propfilter->allof);
4653 paramfilter = paramfilter->next) {
4654
4655 pass = apply_paramfilter(paramfilter, prop);
4656 }
4657
4658 } while (!pass &&
4659 (prop = icalcomponent_get_next_property(comp, propfilter->kind)));
4660
4661 return pass;
4662 }
4663
in_range(icalcomponent * comp,struct icaltime_span * span,void * rock)4664 static void in_range(icalcomponent *comp __attribute__((unused)),
4665 struct icaltime_span *span __attribute__((unused)),
4666 void *rock)
4667 {
4668 int *pass = (int *) rock;
4669
4670 *pass = 1;
4671 }
4672
apply_comp_timerange(struct comp_filter * compfilter,icalcomponent * comp,struct caldav_data * cdata,struct propfind_ctx * fctx)4673 static int apply_comp_timerange(struct comp_filter *compfilter,
4674 icalcomponent *comp, struct caldav_data *cdata,
4675 struct propfind_ctx *fctx)
4676 {
4677 struct icalperiodtype *range = compfilter->range;
4678 struct icaltimetype dtstart;
4679 struct icaltimetype dtend;
4680 int pass = 0;
4681
4682 if (compfilter->depth == 1) {
4683 /* Use period from cdata */
4684 dtstart = icaltime_from_string(cdata->dtstart);
4685 dtend = icaltime_from_string(cdata->dtend);
4686
4687 if (icaltime_compare(dtstart, range->end) >= 0 ||
4688 icaltime_compare(dtend, range->start) <= 0) {
4689 /* All occurrences start later or end earlier than range */
4690 return 0;
4691 }
4692 if (compfilter->kind == ICAL_VAVAILABILITY_COMPONENT) {
4693 /* Don't try to expand VAVAILABILITY, just mark it as in range */
4694 return 1;
4695 }
4696 if (cdata->comp_flags.recurring) {
4697 if (!(compfilter->prop || compfilter->comp) &&
4698 (icaltime_compare(dtstart, range->start) >= 0 ||
4699 icaltime_compare(dtend, range->end) <= 0)) {
4700 /* An occurrence (possibly override) starts or ends within range
4701 and we don't need to do further filtering of the comp */
4702 return 1;
4703 }
4704
4705 /* Load message containing the resource and parse iCal data */
4706 if (!comp) {
4707 if (!fctx->msg_buf.len) {
4708 mailbox_map_record(fctx->mailbox,
4709 fctx->record, &fctx->msg_buf);
4710 }
4711 if (fctx->msg_buf.len && !fctx->obj) {
4712 fctx->obj =
4713 icalparser_parse_string(buf_cstring(&fctx->msg_buf) +
4714 fctx->record->header_size);
4715 }
4716 if (!fctx->obj) return 0;
4717 comp = icalcomponent_get_first_component(fctx->obj,
4718 compfilter->kind);
4719 }
4720 }
4721 else {
4722 /* Non-recurring component overlaps range */
4723 return 1;
4724 }
4725 }
4726
4727 /* Process component */
4728 if (!comp) return 0;
4729
4730 icalcomponent_foreach_recurrence(comp, range->start, range->end,
4731 in_range, &pass);
4732
4733 return pass;
4734 }
4735
4736 /* See if the current resource matches the specified filter.
4737 * Returns 1 if match, 0 otherwise.
4738 */
apply_compfilter(struct comp_filter * compfilter,icalcomponent * ical,struct caldav_data * cdata,struct propfind_ctx * fctx)4739 static int apply_compfilter(struct comp_filter *compfilter, icalcomponent *ical,
4740 struct caldav_data *cdata, struct propfind_ctx *fctx)
4741 {
4742 int pass = 0;
4743 icalcomponent *comp = NULL;
4744
4745 if (compfilter->comp_type &&
4746 (compfilter->comp_type != cdata->comp_type)) return 0;
4747 if (ical) {
4748 if (compfilter->kind == ICAL_VCALENDAR_COMPONENT) comp = ical;
4749 else comp = icalcomponent_get_first_component(ical, compfilter->kind);
4750 }
4751
4752 /* XXX Do we need to handle X- components?
4753 It doesn't appear that libical currently deals with them.
4754 */
4755
4756 if (compfilter->not_defined) return (comp == NULL);
4757
4758 if (!(compfilter->range || compfilter->prop || compfilter->comp)) return 1;
4759
4760 /* Test each instance of this component (logical OR) */
4761 do {
4762 struct prop_filter *propfilter;
4763 struct comp_filter *subfilter;
4764
4765 pass = compfilter->allof;
4766
4767 if (compfilter->range) {
4768 pass = apply_comp_timerange(compfilter, comp, cdata, fctx);
4769 }
4770
4771 /* Apply each prop-filter, breaking if allof fails or anyof succeeds */
4772 for (propfilter = compfilter->prop;
4773 propfilter && (pass == compfilter->allof);
4774 propfilter = propfilter->next) {
4775
4776 pass = apply_propfilter(propfilter, comp);
4777 }
4778
4779 /* Apply each comp-filter, breaking if allof fails or anyof succeeds */
4780 for (subfilter = compfilter->comp;
4781 subfilter && (pass == compfilter->allof);
4782 subfilter = subfilter->next) {
4783
4784 pass = apply_compfilter(subfilter, comp, cdata, fctx);
4785 }
4786
4787 } while (!pass &&
4788 (comp = icalcomponent_get_next_component(ical, compfilter->kind)));
4789
4790 return pass;
4791 }
4792
4793 /* See if the current resource matches the specified filter.
4794 * Returns 1 if match, 0 otherwise.
4795 */
apply_calfilter(struct propfind_ctx * fctx,void * data)4796 static int apply_calfilter(struct propfind_ctx *fctx, void *data)
4797 {
4798 struct calquery_filter *calfilter =
4799 (struct calquery_filter *) fctx->filter_crit;
4800 struct caldav_data *cdata = (struct caldav_data *) data;
4801 icalcomponent *ical = fctx->obj;
4802
4803 if (calfilter->comp_types) {
4804 /* Check if we can short-circuit based on
4805 comp-filter(s) vs component type of resource */
4806 if (!(cdata->comp_type & calfilter->comp_types)) return 0;
4807 if (calfilter->comp->allof &&
4808 (cdata->comp_type & CAL_COMP_REAL) !=
4809 (calfilter->comp_types & CAL_COMP_REAL)) {
4810 return 0;
4811 }
4812 }
4813
4814 if ((calfilter->flags & PARSE_ICAL) || cdata->comp_flags.recurring) {
4815 /* Load message containing the resource and parse iCal data */
4816 if (!ical) {
4817 if (!fctx->msg_buf.len)
4818 mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf);
4819 if (!fctx->msg_buf.len) return 0;
4820
4821 ical = fctx->obj =
4822 icalparser_parse_string(buf_cstring(&fctx->msg_buf) +
4823 fctx->record->header_size);
4824 }
4825 if (!ical) return 0;
4826 }
4827
4828 return apply_compfilter(calfilter->comp, ical, cdata, fctx);
4829 }
4830
4831
free_compfilter(struct comp_filter * comp)4832 static void free_compfilter(struct comp_filter *comp)
4833 {
4834 struct comp_filter *subcomp, *nextc;
4835 struct prop_filter *prop, *nextp;
4836
4837 if (!comp) return;
4838
4839 xmlFree(comp->name);
4840 if (comp->range) free(comp->range);
4841
4842 for (prop = comp->prop; prop; prop = nextp) {
4843 nextp = prop->next;
4844
4845 if (prop->other) free(prop->other);
4846 dav_free_propfilter(prop);
4847 }
4848 for (subcomp = comp->comp; subcomp; subcomp = nextc) {
4849 nextc = subcomp->next;
4850
4851 free_compfilter(subcomp);
4852 }
4853
4854 free(comp);
4855 }
4856
4857
4858 /* dav_foreach() callback to find props on a CalDAV resource
4859 *
4860 * This function will strip any known VTIMEZONEs from the existing resource
4861 * and store as a new resource before returning properties
4862 */
caldav_propfind_by_resource(void * rock,void * data)4863 static int caldav_propfind_by_resource(void *rock, void *data)
4864 {
4865 struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
4866 struct caldav_data *cdata = (struct caldav_data *) data;
4867
4868 if (sqlite3_libversion_number() < 3003008) {
4869 /* Can't write to a table while a SELECT is active */
4870 goto done;
4871 }
4872
4873 if (cdata->dav.imap_uid && !cdata->comp_flags.tzbyref) {
4874 struct index_record record;
4875 int r;
4876
4877 /* Relock index for writing */
4878 if (!mailbox_index_islocked(fctx->mailbox, 1)) {
4879 mailbox_unlock_index(fctx->mailbox, NULL);
4880 r = mailbox_lock_index(fctx->mailbox, LOCK_EXCLUSIVE);
4881 if (r) {
4882 syslog(LOG_ERR, "relock index(%s) failed: %s",
4883 fctx->mailbox->name, error_message(r));
4884 goto done;
4885 }
4886 }
4887
4888 if (!fctx->record) {
4889 /* Fetch index record for the resource */
4890 r = mailbox_find_index_record(fctx->mailbox,
4891 cdata->dav.imap_uid, &record);
4892 /* XXX Check errors */
4893
4894 fctx->record = r ? NULL : &record;
4895 }
4896
4897 if (fctx->record) {
4898 char *schedule_address = NULL;
4899 icalcomponent *ical =
4900 record_to_ical(fctx->mailbox, fctx->record, &schedule_address);
4901 struct transaction_t txn;
4902
4903 if (!ical) {
4904 syslog(LOG_NOTICE,
4905 "Unable to parse iCal %s:%u prior to stripping TZ",
4906 fctx->mailbox->name, fctx->record->uid);
4907 free(schedule_address);
4908 goto done;
4909 }
4910
4911 memset(&txn, 0, sizeof(struct transaction_t));
4912 txn.req_hdrs = spool_new_hdrcache();
4913
4914 caldav_store_resource(&txn, ical, fctx->mailbox,
4915 cdata->dav.resource, fctx->davdb,
4916 TZ_STRIP | (!cdata->sched_tag ? NEW_STAG : 0),
4917 schedule_address);
4918 spool_free_hdrcache(txn.req_hdrs);
4919 buf_free(&txn.buf);
4920 free(schedule_address);
4921
4922 icalcomponent_free(ical);
4923
4924 caldav_lookup_resource(fctx->davdb, fctx->mailbox->name,
4925 cdata->dav.resource, &cdata, 0);
4926 fctx->record = NULL;
4927 }
4928 }
4929
4930 done:
4931 return propfind_by_resource(rock, data);
4932 }
4933
4934
4935 /* Callback to fetch DAV:getcontenttype */
propfind_getcontenttype(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)4936 static int propfind_getcontenttype(const xmlChar *name, xmlNsPtr ns,
4937 struct propfind_ctx *fctx,
4938 xmlNodePtr prop __attribute__((unused)),
4939 xmlNodePtr resp __attribute__((unused)),
4940 struct propstat propstat[],
4941 void *rock __attribute__((unused)))
4942 {
4943 buf_setcstr(&fctx->buf, ICALENDAR_CONTENT_TYPE);
4944
4945 if (fctx->data) {
4946 struct caldav_data *cdata = (struct caldav_data *) fctx->data;
4947 const char *comp = NULL;
4948
4949 switch (cdata->comp_type) {
4950 case CAL_COMP_VEVENT: comp = "VEVENT"; break;
4951 case CAL_COMP_VTODO: comp = "VTODO"; break;
4952 case CAL_COMP_VJOURNAL: comp = "VJOURNAL"; break;
4953 case CAL_COMP_VFREEBUSY: comp = "VFREEBUSY"; break;
4954 case CAL_COMP_VAVAILABILITY: comp = "VAVAILABILITY"; break;
4955 case CAL_COMP_VPOLL: comp = "VPOLL"; break;
4956 }
4957
4958 if (comp) buf_printf(&fctx->buf, "; component=%s", comp);
4959 }
4960
4961 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
4962 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
4963
4964 return 0;
4965 }
4966
4967
4968 /* Callback to fetch DAV:resourcetype */
propfind_restype(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)4969 static int propfind_restype(const xmlChar *name, xmlNsPtr ns,
4970 struct propfind_ctx *fctx,
4971 xmlNodePtr prop __attribute__((unused)),
4972 xmlNodePtr resp,
4973 struct propstat propstat[],
4974 void *rock)
4975 {
4976 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
4977 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
4978
4979 if (!fctx->record) {
4980 xmlNewChild(node, NULL, BAD_CAST "collection", NULL);
4981
4982 if (fctx->req_tgt->collection &&
4983 fctx->mbentry->mbtype == MBTYPE_CALENDAR) {
4984 ensure_ns(fctx->ns, NS_CALDAV,
4985 resp ? resp->parent : node->parent, XML_NS_CALDAV, "C");
4986 if (!strcmp(fctx->req_tgt->collection, SCHED_INBOX)) {
4987 xmlNewChild(node, fctx->ns[NS_CALDAV],
4988 BAD_CAST "schedule-inbox", NULL);
4989 }
4990 else if (!strcmp(fctx->req_tgt->collection, SCHED_OUTBOX)) {
4991 xmlNewChild(node, fctx->ns[NS_CALDAV],
4992 BAD_CAST "schedule-outbox", NULL);
4993 }
4994 else {
4995 xmlNewChild(node, fctx->ns[NS_CALDAV],
4996 BAD_CAST "calendar", NULL);
4997 if (rock) {
4998 /* Called from PROPFIND - include "shared[-owner]" type */
4999 xml_add_shareaccess(fctx, resp, node, 1 /* legacy */);
5000 }
5001 }
5002 }
5003 }
5004
5005 return 0;
5006 }
5007
5008 #define PROP_NOVALUE (1<<31)
5009
parse_partial_comp(xmlNodePtr node)5010 static struct partial_comp_t *parse_partial_comp(xmlNodePtr node)
5011 {
5012 xmlChar *prop;
5013 struct partial_comp_t *pcomp;
5014
5015 prop = xmlGetProp(node, BAD_CAST "name");
5016 if (!prop) return NULL;
5017
5018 pcomp = xzmalloc(sizeof(struct partial_comp_t));
5019 pcomp->kind = icalcomponent_string_to_kind((char *) prop);
5020 xmlFree(prop);
5021
5022 for (node = xmlFirstElementChild(node); node;
5023 node = xmlNextElementSibling(node)) {
5024 if (!xmlStrcmp(node->name, BAD_CAST "allprop")) {
5025 arrayu64_add(&pcomp->props, ICAL_ANY_PROPERTY);
5026 }
5027 else if (!xmlStrcmp(node->name, BAD_CAST "prop")) {
5028 uint64_t kind = ICAL_NO_PROPERTY;
5029
5030 prop = xmlGetProp(node, BAD_CAST "name");
5031 if (prop) {
5032 kind = icalproperty_string_to_kind((char *) prop);
5033 xmlFree(prop);
5034
5035 prop = xmlGetProp(node, BAD_CAST "novalue");
5036 if (prop) {
5037 if (!xmlStrcmp(prop, BAD_CAST "yes")) {
5038 /* Set highest order bit to encode "novalue" */
5039 kind |= PROP_NOVALUE;
5040 }
5041 xmlFree(prop);
5042 }
5043 }
5044 arrayu64_add(&pcomp->props, kind);
5045 }
5046 else if (!xmlStrcmp(node->name, BAD_CAST "allcomp")) {
5047 pcomp->child = xzmalloc(sizeof(struct partial_comp_t));
5048 pcomp->child->kind = ICAL_ANY_COMPONENT;
5049 break;
5050 }
5051 else if (!xmlStrcmp(node->name, BAD_CAST "comp")) {
5052 struct partial_comp_t *child = parse_partial_comp(node);
5053 child->sibling = pcomp->child;
5054 pcomp->child = child;
5055 }
5056 }
5057
5058 return pcomp;
5059 }
5060
prune_properties(icalcomponent * parent,struct partial_comp_t * pcomp)5061 static void prune_properties(icalcomponent *parent,
5062 struct partial_comp_t *pcomp)
5063 {
5064 icalcomponent *comp, *next;
5065 int n, size = arrayu64_size(&pcomp->props);
5066
5067 if (!size || arrayu64_nth(&pcomp->props, 0) != ICAL_ANY_PROPERTY) {
5068 /* Strip unwanted properties from component */
5069 icalproperty *prop, *nextprop;
5070
5071 for (prop = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
5072 prop; prop = nextprop) {
5073 nextprop =
5074 icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY);
5075
5076 uint64_t kind;
5077
5078 for (n = 0; n < size; n++) {
5079 kind = arrayu64_nth(&pcomp->props, n);
5080 /* Highest order bit encodes "novalue" */
5081 if ((kind & ~PROP_NOVALUE) == icalproperty_isa(prop)) break;
5082 }
5083
5084 if (n >= size) {
5085 /* Don't want this property, remove it */
5086 icalcomponent_remove_property(parent, prop);
5087 icalproperty_free(prop);
5088 }
5089 else if (kind & PROP_NOVALUE) {
5090 /* Don't want value for this property, remove it */
5091 /* XXX This requires libical (<= 2.x) to be compiled with
5092 ICAL_ALLOW_EMPTY_PROPERTIES=true
5093 otherwise icalproperty_as_ical_string() will output
5094 "ERROR: No Value" as the property value */
5095 icalproperty_set_value(prop, icalvalue_new(ICAL_NO_VALUE));
5096 }
5097 }
5098 }
5099
5100 if (pcomp->child && pcomp->child->kind == ICAL_ANY_COMPONENT) return;
5101
5102 /* Strip unwanted components from component */
5103 for (comp = icalcomponent_get_first_component(parent, ICAL_ANY_COMPONENT);
5104 comp; comp = next) {
5105 icalcomponent_kind kind = icalcomponent_isa(comp);
5106 struct partial_comp_t *child;
5107
5108 next = icalcomponent_get_next_component(parent, ICAL_ANY_COMPONENT);
5109
5110 for (child = pcomp->child; child; child = child->sibling) {
5111 if (child->kind == kind) break;
5112 }
5113 if (child) prune_properties(comp, child);
5114 else {
5115 icalcomponent_remove_component(parent, comp);
5116 icalcomponent_free(comp);
5117 }
5118 }
5119 }
5120
expand_cb(icalcomponent * comp,icaltimetype start,icaltimetype end,void * rock)5121 static int expand_cb(icalcomponent *comp,
5122 icaltimetype start, icaltimetype end, void *rock)
5123 {
5124 icalcomponent *ical = icalcomponent_get_parent(comp);
5125 icalcomponent *expanded_ical = (icalcomponent *) rock;
5126 icalproperty *prop, *nextprop, *recurid = NULL;
5127 struct icaldatetimeperiodtype dtp;
5128 icaltimetype dtstart;
5129
5130 start = icaltime_convert_to_zone(start, utc_zone);
5131 end = icaltime_convert_to_zone(end, utc_zone);
5132
5133 /* Fetch/set/remove interesting properties */
5134 for (prop = icalcomponent_get_first_property(comp, ICAL_ANY_PROPERTY);
5135 prop; prop = nextprop) {
5136 nextprop = icalcomponent_get_next_property(comp, ICAL_ANY_PROPERTY);
5137
5138 switch (icalproperty_isa(prop)) {
5139 case ICAL_DTSTART_PROPERTY:
5140 /* Fetch exiting DTSTART (might be master) */
5141 dtp = icalproperty_get_datetimeperiod(prop);
5142 dtstart = icaltime_convert_to_zone(dtp.time, utc_zone);
5143
5144 /* Set DTSTART to be for this occurrence (in UTC) */
5145 icalproperty_set_dtstart(prop, start);
5146 icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
5147 break;
5148
5149 case ICAL_DTEND_PROPERTY:
5150 /* Set DTEND to be for this occurrence (in UTC) */
5151 icalproperty_set_dtend(prop, end);
5152 icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
5153 break;
5154
5155 case ICAL_DURATION_PROPERTY:
5156 /* Set DURATION to be for this occurrence */
5157 icalproperty_set_duration(prop, icaltime_subtract(end, start));
5158 break;
5159
5160 case ICAL_RECURRENCEID_PROPERTY:
5161 /* Reset RECURRENCE-ID of this override to UTC */
5162 dtp = icalproperty_get_datetimeperiod(prop);
5163 dtp.time = icaltime_convert_to_zone(dtp.time, utc_zone);
5164 icalproperty_set_recurrenceid(prop, dtp.time);
5165 icalproperty_remove_parameter_by_kind(prop, ICAL_TZID_PARAMETER);
5166 recurid = prop;
5167
5168 /* Remove component from existing ical (we can use it as-is) */
5169 icalcomponent_remove_component(ical, comp);
5170 break;
5171
5172 case ICAL_RRULE_PROPERTY:
5173 case ICAL_RDATE_PROPERTY:
5174 case ICAL_EXRULE_PROPERTY:
5175 case ICAL_EXDATE_PROPERTY:
5176 /* We don't want any recurrence rule properties */
5177 icalcomponent_remove_property(comp, prop);
5178 icalproperty_free(prop);
5179 break;
5180
5181 default:
5182 break;
5183 }
5184 }
5185
5186 if (!recurid) {
5187 /* Clone the master component */
5188 comp = icalcomponent_new_clone(comp);
5189 if (icaltime_compare(start, dtstart)) {
5190 /* Not the first instance - set RECURRENCE-ID */
5191 icalcomponent_set_recurrenceid(comp, start);
5192 }
5193 }
5194
5195 /* Append the component to expanded ical */
5196 icalcomponent_add_component(expanded_ical, comp);
5197
5198 return 1;
5199 }
5200
5201 /* Expand recurrences of ical in range.
5202 NOTE: expand_cb() is destructive of ical as it builds expanded_ical */
expand_caldata(icalcomponent ** ical,struct icalperiodtype range)5203 static icalcomponent *expand_caldata(icalcomponent **ical,
5204 struct icalperiodtype range)
5205 {
5206 icalcomponent *expanded_ical =
5207 icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
5208 icalproperty_new_version("2.0"),
5209 icalproperty_new_prodid(ical_prodid),
5210 0);
5211
5212 /* Copy over any CALSCALE property */
5213 icalproperty *prop =
5214 icalcomponent_get_first_property(*ical, ICAL_CALSCALE_PROPERTY);
5215 if (prop)
5216 icalcomponent_add_property(expanded_ical, icalproperty_new_clone(prop));
5217
5218 icalcomponent_myforeach(*ical, range, NULL, expand_cb, expanded_ical);
5219 icalcomponent_free(*ical);
5220 *ical = expanded_ical;
5221
5222 return *ical;
5223 }
5224
limit_caldata(icalcomponent * ical,struct icalperiodtype * limit)5225 static void limit_caldata(icalcomponent *ical, struct icalperiodtype *limit)
5226 {
5227 icaltime_span limitspan;
5228 icaltimetype dtstart, dtend, recurid;
5229 struct icaldurationtype dtduration = icaldurationtype_null_duration();
5230 icalcomponent *comp, *nextcomp;
5231 icalcomponent_kind kind;
5232
5233 limitspan.start = icaltime_as_timet_with_zone(limit->start, utc_zone);
5234 limitspan.end = icaltime_as_timet_with_zone(limit->end, utc_zone);
5235
5236 comp = icalcomponent_get_first_real_component(ical);
5237 kind = icalcomponent_isa(comp);
5238
5239 /* Find master component */
5240 for (; comp; comp = icalcomponent_get_next_component(ical, kind)) {
5241
5242 recurid = icalcomponent_get_recurrenceid_with_zone(comp);
5243
5244 if (icaltime_is_null_time(recurid)) {
5245 /* Calculate duration of master component */
5246 dtstart = icalcomponent_get_dtstart(comp);
5247 dtend = icalcomponent_get_dtend(comp);
5248 dtduration = icaltime_subtract(dtend, dtstart);
5249 break;
5250 }
5251 }
5252
5253 /* Check spans of each override against our limit span */
5254 for (comp = icalcomponent_get_first_component(ical, kind);
5255 comp; comp = nextcomp) {
5256
5257 icaltime_span recurspan;
5258
5259 nextcomp = icalcomponent_get_next_component(ical, kind);
5260 recurid = icalcomponent_get_recurrenceid_with_zone(comp);
5261
5262 /* Skip master component */
5263 if (icaltime_is_null_time(recurid)) continue;
5264
5265 /* Check span of override */
5266 dtstart = icalcomponent_get_dtstart(comp);
5267 dtend = icalcomponent_get_dtend(comp);
5268 recurspan = icaltime_span_new(dtstart, dtend, 1);
5269 if (icaltime_span_overlaps(&recurspan, &limitspan)) continue;
5270
5271 /* Check span of original occurrence */
5272 dtstart = recurid;
5273 dtend = icaltime_add(dtstart, dtduration);
5274 recurspan = icaltime_span_new(dtstart, dtend, 1);
5275 if (icaltime_span_overlaps(&recurspan, &limitspan)) continue;
5276
5277 /* Remove this component (doesn't overlap limit range) */
5278 icalcomponent_remove_component(ical, comp);
5279 icalcomponent_free(comp);
5280 }
5281 }
5282
5283 /* Callback to prescreen/fetch CALDAV:calendar-data */
propfind_scheduser(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5284 static int propfind_scheduser(const xmlChar *name, xmlNsPtr ns,
5285 struct propfind_ctx *fctx,
5286 xmlNodePtr prop __attribute__((unused)),
5287 xmlNodePtr resp __attribute__((unused)),
5288 struct propstat propstat[],
5289 void *rock __attribute__((unused)))
5290 {
5291 struct buf buf = BUF_INITIALIZER;
5292 int rc = HTTP_NOT_FOUND;
5293
5294 if (propstat && fctx->mailbox && fctx->record) {
5295 message_t *m = message_new_from_record(fctx->mailbox, fctx->record);
5296
5297 message_get_field(m, "x-schedule-user-address",
5298 MESSAGE_DECODED|MESSAGE_TRIM, &buf);
5299
5300 message_unref(&m);
5301 }
5302
5303 if (buf.len) {
5304 rc = 0;
5305 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5306 name, ns, NULL, 0);
5307 buf_reset(&fctx->buf);
5308 const char *address = buf_cstring(&buf);
5309 if (!strncasecmp(address, "mailto:", 7)) address += 7;
5310 buf_printf(&fctx->buf, "mailto:%s", address);
5311 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5312 }
5313
5314 buf_free(&buf);
5315
5316 return rc;
5317 }
5318
5319 /* Callback to prescreen/fetch CALDAV:calendar-data */
propfind_caldata(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5320 static int propfind_caldata(const xmlChar *name, xmlNsPtr ns,
5321 struct propfind_ctx *fctx,
5322 xmlNodePtr prop,
5323 xmlNodePtr resp __attribute__((unused)),
5324 struct propstat propstat[],
5325 void *rock)
5326 {
5327 struct partial_caldata_t *partial = (struct partial_caldata_t *) rock;
5328 struct caldav_data *cdata = (struct caldav_data *) fctx->data;
5329 static unsigned need_tz = 0;
5330 const char *data = NULL;
5331 size_t datalen = 0;
5332 int r = 0;
5333
5334 if (propstat) {
5335 icalcomponent *ical = NULL;
5336
5337 if (!fctx->record) return HTTP_NOT_FOUND;
5338
5339 if (!fctx->msg_buf.len)
5340 mailbox_map_record(fctx->mailbox, fctx->record, &fctx->msg_buf);
5341 if (!fctx->msg_buf.len) return HTTP_SERVER_ERROR;
5342
5343 data = fctx->msg_buf.s + fctx->record->header_size;
5344 datalen = fctx->record->size - fctx->record->header_size;
5345
5346 if (need_tz) {
5347 if (cdata->comp_flags.tzbyref) {
5348 /* Add VTIMEZONE components for known TZIDs */
5349 struct timezone_rock tzrock = { NULL, NULL };
5350 icalcomponent *comp, *next;
5351 icalcomponent_kind kind;
5352
5353 if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5354 ical = fctx->obj;
5355 tzrock.new = ical;
5356
5357 comp = icalcomponent_get_first_real_component(ical);
5358 kind = icalcomponent_isa(comp);
5359 for (; comp; comp = next) {
5360 next = icalcomponent_get_next_component(ical, kind);
5361 icalcomponent_foreach_tzid(comp, &add_timezone, &tzrock);
5362 }
5363 }
5364 }
5365 else if (!cdata->comp_flags.tzbyref &&
5366 (namespace_calendar.allow & ALLOW_CAL_NOTZ)) {
5367 /* Strip all VTIMEZONE components for known TZIDs */
5368 if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5369 ical = fctx->obj;
5370
5371 strip_vtimezones(ical);
5372 }
5373
5374 if (!icaltime_is_null_time(partial->range.start)) {
5375 /* Expand/limit recurrence set */
5376 if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5377 ical = fctx->obj;
5378
5379 if (partial->expand) {
5380 fctx->obj = expand_caldata(&ical, partial->range);
5381 }
5382 else limit_caldata(ical, &partial->range);
5383 }
5384
5385 if (partial->comp) {
5386 /* Limit returned properties */
5387 if (!fctx->obj) fctx->obj = icalparser_parse_string(data);
5388 ical = fctx->obj;
5389 prune_properties(ical, partial->comp);
5390 }
5391
5392 if (ical) {
5393 /* Create iCalendar data from new ical component */
5394 data = icalcomponent_as_ical_string(ical);
5395 datalen = strlen(data);
5396 }
5397 }
5398 else if (prop) {
5399 /* Prescreen "property" request - read partial/expand children */
5400 xmlNodePtr node;
5401
5402 /* Check for optional CalDAV-Timezones header */
5403 const char **hdr =
5404 spool_getheader(fctx->txn->req_hdrs, "CalDAV-Timezones");
5405 if (hdr && !strcmp(hdr[0], "T")) need_tz = 1;
5406 else need_tz = 0;
5407
5408 /* Initialize expand to be "empty" */
5409 partial->range.start = icaltime_null_time();
5410 partial->range.end = icaltime_null_time();
5411 partial->comp = NULL;
5412
5413 /* Check for and parse child elements of CALDAV:calendar-data */
5414 for (node = xmlFirstElementChild(prop); node;
5415 node = xmlNextElementSibling(node)) {
5416 xmlChar *prop;
5417
5418 if (!xmlStrcmp(node->name, BAD_CAST "expand") ||
5419 !xmlStrcmp(node->name, BAD_CAST "limit-recurrence-set")) {
5420 partial->expand = (node->name[0] == 'e');
5421 prop = xmlGetProp(node, BAD_CAST "start");
5422 if (!prop) return (*fctx->ret = HTTP_BAD_REQUEST);
5423 partial->range.start = icaltime_from_string((char *) prop);
5424 xmlFree(prop);
5425
5426 prop = xmlGetProp(node, BAD_CAST "end");
5427 if (!prop) return (*fctx->ret = HTTP_BAD_REQUEST);
5428 partial->range.end = icaltime_from_string((char *) prop);
5429 xmlFree(prop);
5430 }
5431 else if (!xmlStrcmp(node->name, BAD_CAST "comp")) {
5432 partial->comp = parse_partial_comp(node);
5433 if (!partial->comp ||
5434 partial->comp->kind != ICAL_VCALENDAR_COMPONENT) {
5435 return (*fctx->ret = HTTP_BAD_REQUEST);
5436 }
5437 }
5438 else if (!xmlStrcmp(node->name, BAD_CAST "limit-freebusy-set")) {
5439 syslog(LOG_NOTICE,
5440 "Client attempted to use CALDAV:limit-freebusy-set");
5441 return (*fctx->ret = HTTP_NOT_IMPLEMENTED);
5442 }
5443 }
5444
5445 if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
5446 /* We want to strip known VTIMEZONEs */
5447 fctx->proc_by_resource = &caldav_propfind_by_resource;
5448 }
5449 }
5450 else {
5451 /* Cleanup "property" request - free partial component structure */
5452 struct partial_comp_t *pcomp, *child, *sibling;
5453
5454 for (pcomp = partial->comp; pcomp; pcomp = child) {
5455 child = pcomp->child;
5456
5457 do {
5458 sibling = pcomp->sibling;
5459 arrayu64_fini(&pcomp->props);
5460 free(pcomp);
5461 } while ((pcomp = sibling));
5462 }
5463
5464 return 0;
5465 }
5466
5467 r = propfind_getdata(name, ns, fctx, prop, propstat, caldav_mime_types,
5468 CALDAV_SUPP_DATA, data, datalen);
5469
5470 return r;
5471 }
5472
5473
5474 /* Callback to fetch CALDAV:calendar-home-set,
5475 * CALDAV:schedule-inbox-URL, CALDAV:schedule-outbox-URL,
5476 * and CALDAV:schedule-default-calendar-URL
5477 */
propfind_calurl(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5478 int propfind_calurl(const xmlChar *name, xmlNsPtr ns,
5479 struct propfind_ctx *fctx,
5480 xmlNodePtr prop,
5481 xmlNodePtr resp __attribute__((unused)),
5482 struct propstat propstat[], void *rock)
5483 {
5484 const char *cal = (const char *) rock;
5485 xmlNodePtr node;
5486 /* NOTE: calbuf needs to stay in scope until 'cal' is finished with */
5487 struct buf calbuf = BUF_INITIALIZER;
5488 int ret = HTTP_NOT_FOUND; /* error condition if we bail early */
5489
5490 if (!(namespace_calendar.enabled && httpd_userid))
5491 goto done;
5492
5493 if (cal) {
5494 /* named calendars are only used for scheduling */
5495 if (!(namespace_calendar.allow & ALLOW_CAL_SCHED))
5496 goto done;
5497
5498 /* check for renamed calendars - property on the homeset */
5499 const char *annotname = NULL;
5500 if (!strcmp(cal, SCHED_DEFAULT)) {
5501 annotname =
5502 DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-default-calendar";
5503 }
5504 else if (!strcmp(cal, SCHED_INBOX))
5505 annotname = DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-inbox";
5506 else if (!strcmp(cal, SCHED_OUTBOX))
5507 annotname = DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-outbox";
5508
5509 if (annotname) {
5510 char *mailboxname = caldav_mboxname(httpd_userid, NULL);
5511 int r = annotatemore_lookupmask(mailboxname, annotname,
5512 httpd_userid, &calbuf);
5513 free(mailboxname);
5514 if (!r && calbuf.len) {
5515 buf_putc(&calbuf, '/');
5516 cal = buf_cstring(&calbuf);
5517 }
5518 }
5519 }
5520
5521 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5522 name, ns, NULL, 0);
5523
5524 buf_reset(&fctx->buf);
5525 if (strchr(httpd_userid, '@') || !httpd_extradomain) {
5526 buf_printf(&fctx->buf, "%s/%s/%s/", namespace_calendar.prefix,
5527 USER_COLLECTION_PREFIX, httpd_userid);
5528 }
5529 else {
5530 buf_printf(&fctx->buf, "%s/%s/%s@%s/", namespace_calendar.prefix,
5531 USER_COLLECTION_PREFIX, httpd_userid, httpd_extradomain);
5532 }
5533 if (cal) buf_appendcstr(&fctx->buf, cal);
5534
5535 if ((fctx->mode == PROPFIND_EXPAND) && xmlFirstElementChild(prop)) {
5536 /* Return properties for this URL */
5537 expand_property(prop, fctx, &namespace_calendar, buf_cstring(&fctx->buf),
5538 &caldav_parse_path, caldav_props, node, cal ? 1 : 0);
5539 }
5540 else {
5541 /* Return just the URL */
5542 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5543 }
5544
5545 ret = 0;
5546 done:
5547 buf_free(&calbuf);
5548
5549 return ret;
5550 }
5551
5552
5553 /* Callback to fetch CALDAV:schedule-default-calendar-URL */
propfind_scheddefault(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5554 static int propfind_scheddefault(const xmlChar *name, xmlNsPtr ns,
5555 struct propfind_ctx *fctx,
5556 xmlNodePtr prop, xmlNodePtr resp,
5557 struct propstat propstat[],
5558 void *rock __attribute__((unused)))
5559 {
5560 /* Only defined on CALDAV:schedule-inbox-URL */
5561 if (fctx->req_tgt->flags != TGT_SCHED_INBOX) return HTTP_NOT_FOUND;
5562
5563 return propfind_calurl(name, ns, fctx,
5564 prop, resp, propstat, SCHED_DEFAULT);
5565 }
5566
5567
5568 /* Callback to fetch CALDAV:supported-calendar-component-set[s] */
propfind_calcompset(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5569 static int propfind_calcompset(const xmlChar *name, xmlNsPtr ns,
5570 struct propfind_ctx *fctx,
5571 xmlNodePtr prop __attribute__((unused)),
5572 xmlNodePtr resp __attribute__((unused)),
5573 struct propstat propstat[],
5574 void *rock __attribute__((unused)))
5575 {
5576 struct buf attrib = BUF_INITIALIZER;
5577 const char *prop_annot =
5578 DAV_ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
5579 unsigned long types = 0;
5580 xmlNodePtr set, node;
5581 const struct cal_comp_t *comp;
5582 int r = 0;
5583
5584 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5585
5586 r = annotatemore_lookupmask(fctx->mbentry->name, prop_annot,
5587 httpd_userid, &attrib);
5588 if (r) return HTTP_SERVER_ERROR;
5589
5590 if (attrib.len) {
5591 types = strtoul(buf_cstring(&attrib), NULL, 10);
5592 }
5593 else {
5594 types = -1; /* ALL components types */
5595
5596 /* Apple clients don't like VPOLL */
5597 types &= ~CAL_COMP_VPOLL;
5598 }
5599
5600 buf_free(&attrib);
5601
5602 if (!types) return HTTP_NOT_FOUND;
5603
5604 set = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5605 name, ns, NULL, 0);
5606 if (!resp) {
5607 ensure_ns(fctx->ns, NS_CALDAV, set->parent, XML_NS_CALDAV, "C");
5608 xmlSetNs(set, fctx->ns[NS_CALDAV]);
5609 }
5610
5611 /* Create "comp" elements from the stored bitmask */
5612 for (comp = cal_comps; comp->name; comp++) {
5613 if (types & comp->type) {
5614 node = xmlNewChild(set, fctx->ns[NS_CALDAV],
5615 BAD_CAST "comp", NULL);
5616 xmlNewProp(node, BAD_CAST "name", BAD_CAST comp->name);
5617 }
5618 }
5619
5620 return 0;
5621 }
5622
5623
5624 /* Callback to write supported-calendar-component-set property */
proppatch_calcompset(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)5625 static int proppatch_calcompset(xmlNodePtr prop, unsigned set,
5626 struct proppatch_ctx *pctx,
5627 struct propstat propstat[],
5628 void *rock __attribute__((unused)))
5629 {
5630 unsigned precond = 0, force = 0;
5631 xmlChar *attr = xmlGetProp(prop, BAD_CAST "force");
5632
5633 /* Check if we want to force changing of a protected property.
5634 This is mainly for our list_calendars() JavaScript client. */
5635 if (attr) {
5636 if (!xmlStrcmp(attr, BAD_CAST "yes") &&
5637 mboxname_userownsmailbox(httpd_userid, pctx->mailbox->name)) {
5638 force = 1;
5639 }
5640 xmlFree(attr);
5641 }
5642
5643 if (set && (force || (pctx->txn->meth != METH_PROPPATCH))) {
5644 /* "Writeable" for MKCOL/MKCALENDAR only */
5645 xmlNodePtr cur;
5646 unsigned long types = 0;
5647
5648 /* Work through the given list of components */
5649 for (cur = prop->children; cur; cur = cur->next) {
5650 xmlChar *name;
5651 const struct cal_comp_t *comp;
5652
5653 /* Make sure its a "comp" element with a "name" */
5654 if (cur->type != XML_ELEMENT_NODE) continue;
5655 if (xmlStrcmp(cur->name, BAD_CAST "comp") ||
5656 !(name = xmlGetProp(cur, BAD_CAST "name"))) break;
5657
5658 /* Make sure we have a valid component type */
5659 for (comp = cal_comps;
5660 comp->name && xmlStrcmp(name, BAD_CAST comp->name); comp++);
5661 xmlFree(name);
5662
5663 if (comp->name) types |= comp->type; /* found match in our list */
5664 else break; /* no match - invalid type */
5665 }
5666
5667 if (!cur) {
5668 /* All component types are valid */
5669 char typestr[(sizeof(unsigned long) * 8) / 3 + 1];
5670 sprintf(typestr, "%lu", types);
5671
5672 proppatch_todb(prop, set, pctx, propstat, (void *) typestr);
5673
5674 return 0;
5675 }
5676
5677 /* Invalid component type */
5678 precond = CALDAV_SUPP_COMP;
5679 }
5680 else {
5681 /* Protected property */
5682 precond = DAV_PROT_PROP;
5683 }
5684
5685 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV], &propstat[PROPSTAT_FORBID],
5686 prop->name, prop->ns, NULL, precond);
5687
5688 *pctx->ret = HTTP_FORBIDDEN;
5689
5690 return 0;
5691 }
5692
5693 /* Callback to fetch CALDAV:supported-calendar-data */
propfind_suppcaldata(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5694 static int propfind_suppcaldata(const xmlChar *name, xmlNsPtr ns,
5695 struct propfind_ctx *fctx,
5696 xmlNodePtr prop __attribute__((unused)),
5697 xmlNodePtr resp __attribute__((unused)),
5698 struct propstat propstat[],
5699 void *rock __attribute__((unused)))
5700 {
5701 xmlNodePtr node;
5702 struct mime_type_t *mime;
5703
5704 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5705
5706 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5707 name, ns, NULL, 0);
5708
5709 for (mime = caldav_mime_types; mime->content_type; mime++) {
5710 xmlNodePtr type = xmlNewChild(node, fctx->ns[NS_CALDAV],
5711 BAD_CAST "calendar-data", NULL);
5712
5713 /* Trim any charset from content-type */
5714 buf_reset(&fctx->buf);
5715 buf_printf(&fctx->buf, "%.*s",
5716 (int) strcspn(mime->content_type, ";"), mime->content_type);
5717
5718 xmlNewProp(type, BAD_CAST "content-type",
5719 BAD_CAST buf_cstring(&fctx->buf));
5720
5721 if (mime->version)
5722 xmlNewProp(type, BAD_CAST "version", BAD_CAST mime->version);
5723 }
5724
5725 return 0;
5726 }
5727
5728
5729 /* Callback to fetch CALDAV:max-resource-size */
propfind_maxsize(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5730 static int propfind_maxsize(const xmlChar *name, xmlNsPtr ns,
5731 struct propfind_ctx *fctx,
5732 xmlNodePtr prop __attribute__((unused)),
5733 xmlNodePtr resp __attribute__((unused)),
5734 struct propstat propstat[],
5735 void *rock __attribute__((unused)))
5736 {
5737 static int maxsize = 0;
5738
5739 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5740
5741 if (!maxsize) {
5742 maxsize = config_getint(IMAPOPT_MAXMESSAGESIZE);
5743 if (!maxsize) maxsize = INT_MAX;
5744 }
5745
5746 buf_reset(&fctx->buf);
5747 buf_printf(&fctx->buf, "%d", maxsize);
5748 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5749 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
5750
5751 return 0;
5752 }
5753
5754
5755 /* Callback to fetch CALDAV:min/max-date-time */
propfind_minmaxdate(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5756 static int propfind_minmaxdate(const xmlChar *name, xmlNsPtr ns,
5757 struct propfind_ctx *fctx,
5758 xmlNodePtr prop __attribute__((unused)),
5759 xmlNodePtr resp __attribute__((unused)),
5760 struct propstat propstat[],
5761 void *rock)
5762 {
5763 struct icaltimetype date;
5764
5765 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5766
5767 date = icaltime_from_timet_with_zone(*((time_t *) rock), 0, utc_zone);
5768
5769 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5770 name, ns, BAD_CAST icaltime_as_ical_string(date), 0);
5771
5772 return 0;
5773 }
5774
5775
5776 /* Callback to fetch CALDAV:schedule-tag */
propfind_schedtag(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5777 static int propfind_schedtag(const xmlChar *name, xmlNsPtr ns,
5778 struct propfind_ctx *fctx,
5779 xmlNodePtr prop __attribute__((unused)),
5780 xmlNodePtr resp __attribute__((unused)),
5781 struct propstat propstat[],
5782 void *rock __attribute__((unused)))
5783 {
5784 struct caldav_data *cdata = (struct caldav_data *) fctx->data;
5785
5786 if (!cdata->organizer || !cdata->sched_tag) return HTTP_NOT_FOUND;
5787
5788 /* add DQUOTEs */
5789 buf_reset(&fctx->buf);
5790 buf_printf(&fctx->buf, "\"%s\"", cdata->sched_tag);
5791
5792 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5793 name, ns, BAD_CAST buf_cstring(&fctx->buf), 0);
5794
5795 return 0;
5796 }
5797
5798
5799 /* Callback to fetch CALDAV:calendar-user-address-set */
propfind_caluseraddr(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5800 int propfind_caluseraddr(const xmlChar *name, xmlNsPtr ns,
5801 struct propfind_ctx *fctx,
5802 xmlNodePtr prop __attribute__((unused)),
5803 xmlNodePtr resp __attribute__((unused)),
5804 struct propstat propstat[],
5805 void *rock __attribute__((unused)))
5806 {
5807 xmlNodePtr node;
5808 struct strlist *domains;
5809
5810 if (!(namespace_calendar.enabled && fctx->req_tgt->userid))
5811 return HTTP_NOT_FOUND;
5812
5813 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5814 name, ns, NULL, 0);
5815
5816 const char *annotname =
5817 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-user-address-set";
5818 char *mailboxname = caldav_mboxname(fctx->req_tgt->userid, NULL);
5819 buf_reset(&fctx->buf);
5820 int r = annotatemore_lookupmask(mailboxname, annotname,
5821 fctx->req_tgt->userid, &fctx->buf);
5822 free(mailboxname);
5823 if (!r && fctx->buf.len) {
5824 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5825 return 0;
5826 }
5827
5828 /* XXX This needs to be done via an LDAP/DB lookup */
5829 if (strchr(fctx->req_tgt->userid, '@')) {
5830 buf_reset(&fctx->buf);
5831 buf_printf(&fctx->buf, "mailto:%s", fctx->req_tgt->userid);
5832 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5833 return 0;
5834 }
5835
5836 if (httpd_extradomain) {
5837 buf_reset(&fctx->buf);
5838 buf_printf(&fctx->buf, "mailto:%s@%s",
5839 fctx->req_tgt->userid, httpd_extradomain);
5840 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5841 return 0;
5842 }
5843
5844 for (domains = cua_domains; domains; domains = domains->next) {
5845 buf_reset(&fctx->buf);
5846 buf_printf(&fctx->buf, "mailto:%s@%s",
5847 fctx->req_tgt->userid, domains->s);
5848
5849 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
5850 }
5851
5852 return 0;
5853 }
5854
5855 /* Callback to fetch CALDAV:calendar-user-type */
propfind_calusertype(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5856 int propfind_calusertype(const xmlChar *name, xmlNsPtr ns,
5857 struct propfind_ctx *fctx,
5858 xmlNodePtr prop __attribute__((unused)),
5859 xmlNodePtr resp __attribute__((unused)),
5860 struct propstat propstat[],
5861 void *rock __attribute__((unused)))
5862 {
5863 const char *type = fctx->req_tgt->userid ? "INDIVIDUAL" : NULL;
5864
5865 if (!namespace_calendar.enabled || !type) return HTTP_NOT_FOUND;
5866
5867 xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5868 name, ns, BAD_CAST type, 0);
5869
5870 return 0;
5871 }
5872
5873
5874 /* Callback to fetch CALDAV:schedule-calendar-transp */
propfind_caltransp(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5875 static int propfind_caltransp(const xmlChar *name, xmlNsPtr ns,
5876 struct propfind_ctx *fctx,
5877 xmlNodePtr prop __attribute__((unused)),
5878 xmlNodePtr resp __attribute__((unused)),
5879 struct propstat propstat[],
5880 void *rock __attribute__((unused)))
5881 {
5882 struct buf attrib = BUF_INITIALIZER;
5883 const char *prop_annot =
5884 DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
5885 xmlNodePtr node;
5886 int r = 0;
5887
5888 if (!fctx->req_tgt->collection) return HTTP_NOT_FOUND;
5889
5890 r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
5891 httpd_userid, &attrib);
5892
5893 if (r) return HTTP_SERVER_ERROR;
5894 if (!attrib.len) return HTTP_NOT_FOUND;
5895
5896 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
5897 name, ns, NULL, 0);
5898 xmlNewChild(node, fctx->ns[NS_CALDAV], BAD_CAST buf_cstring(&attrib), NULL);
5899
5900 buf_free(&attrib);
5901
5902 return 0;
5903 }
5904
5905
5906 /* Callback to write schedule-calendar-transp property */
proppatch_caltransp(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)5907 static int proppatch_caltransp(xmlNodePtr prop, unsigned set,
5908 struct proppatch_ctx *pctx,
5909 struct propstat propstat[],
5910 void *rock __attribute__((unused)))
5911 {
5912 if (pctx->txn->req_tgt.collection && !pctx->txn->req_tgt.resource) {
5913 const xmlChar *value = NULL;
5914
5915 if (set) {
5916 xmlNodePtr cur;
5917
5918 /* Find the value */
5919 for (cur = prop->children; cur; cur = cur->next) {
5920
5921 /* Make sure its a value we understand */
5922 if (cur->type != XML_ELEMENT_NODE) continue;
5923 if (!xmlStrcmp(cur->name, BAD_CAST "opaque") ||
5924 !xmlStrcmp(cur->name, BAD_CAST "transparent")) {
5925 value = cur->name;
5926 break;
5927 }
5928 else {
5929 /* Unknown value */
5930 xml_add_prop(HTTP_CONFLICT, pctx->ns[NS_DAV],
5931 &propstat[PROPSTAT_CONFLICT],
5932 prop->name, prop->ns, NULL, 0);
5933
5934 *pctx->ret = HTTP_FORBIDDEN;
5935
5936 return 0;
5937 }
5938 }
5939 }
5940
5941 proppatch_todb(prop, set, pctx, propstat, (void *) value);
5942 }
5943 else {
5944 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
5945 &propstat[PROPSTAT_FORBID],
5946 prop->name, prop->ns, NULL, 0);
5947
5948 *pctx->ret = HTTP_FORBIDDEN;
5949 }
5950
5951 return 0;
5952 }
5953
5954
5955 /* Callback to prescreen/fetch CALDAV:calendar-timezone */
propfind_timezone(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)5956 static int propfind_timezone(const xmlChar *name, xmlNsPtr ns,
5957 struct propfind_ctx *fctx,
5958 xmlNodePtr prop,
5959 xmlNodePtr resp __attribute__((unused)),
5960 struct propstat propstat[],
5961 void *rock __attribute__((unused)))
5962 {
5963 struct buf attrib = BUF_INITIALIZER;
5964 const char *data = NULL, *msg_base = NULL;
5965 size_t datalen = 0;
5966 int r = 0;
5967
5968 if (propstat) {
5969 const char *prop_annot =
5970 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone";
5971
5972 if (fctx->mailbox && !fctx->record) {
5973 r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
5974 httpd_userid, &attrib);
5975 }
5976
5977 if (r) r = HTTP_SERVER_ERROR;
5978 else if (attrib.len) {
5979 data = buf_cstring(&attrib);
5980 datalen = attrib.len;
5981 }
5982 else if ((namespace_calendar.allow & ALLOW_CAL_NOTZ)) {
5983 /* Check for CALDAV:calendar-timezone-id */
5984 prop_annot = DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone-id";
5985
5986 buf_free(&attrib);
5987
5988 if (fctx->mailbox)
5989 r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
5990 httpd_userid, &attrib);
5991
5992 if (r) r = HTTP_SERVER_ERROR;
5993 else if (!attrib.len) r = HTTP_NOT_FOUND;
5994 else {
5995 const char *path;
5996 int fd;
5997
5998 /* Open and mmap the timezone file */
5999 buf_reset(&fctx->buf);
6000 buf_printf(&fctx->buf, "%s%s/%s.ics",
6001 config_dir, FNAME_ZONEINFODIR, buf_cstring(&attrib));
6002
6003 path = buf_cstring(&fctx->buf);
6004 if ((fd = open(path, O_RDONLY)) == -1) return HTTP_SERVER_ERROR;
6005
6006 map_refresh(fd, 1, &msg_base, &datalen,
6007 MAP_UNKNOWN_LEN, path, NULL);
6008 close(fd);
6009
6010 if (!msg_base) r = HTTP_SERVER_ERROR;
6011
6012 data = msg_base;
6013 }
6014 }
6015 else r = HTTP_NOT_FOUND;
6016 }
6017
6018 if (!r) r = propfind_getdata(name, ns, fctx, prop, propstat,
6019 caldav_mime_types, CALDAV_SUPP_DATA,
6020 data, datalen);
6021
6022 if (msg_base) map_free(&msg_base, &datalen);
6023 buf_free(&attrib);
6024
6025 return r;
6026 }
6027
6028
6029 /* Callback to write calendar-timezone property */
proppatch_timezone(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)6030 static int proppatch_timezone(xmlNodePtr prop, unsigned set,
6031 struct proppatch_ctx *pctx,
6032 struct propstat propstat[],
6033 void *rock __attribute__((unused)))
6034 {
6035 if (pctx->txn->req_tgt.collection && !pctx->txn->req_tgt.resource) {
6036 xmlChar *type, *ver = NULL, *freeme = NULL;
6037 const char *tz = NULL;
6038 struct mime_type_t *mime;
6039 unsigned valid = 1;
6040
6041 type = xmlGetProp(prop, BAD_CAST "content-type");
6042 if (type) ver = xmlGetProp(prop, BAD_CAST "version");
6043
6044 /* Check/find requested MIME type */
6045 for (mime = caldav_mime_types; type && mime->content_type; mime++) {
6046 if (is_mediatype(mime->content_type, (const char *) type)) {
6047 if (ver &&
6048 (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) {
6049 continue;
6050 }
6051 break;
6052 }
6053 }
6054
6055 if (!mime || !mime->content_type) {
6056 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6057 &propstat[PROPSTAT_FORBID],
6058 prop->name, prop->ns, NULL,
6059 CALDAV_SUPP_DATA);
6060 *pctx->ret = HTTP_FORBIDDEN;
6061 valid = 0;
6062 }
6063 else if (set) {
6064 icalcomponent *ical = NULL;
6065 struct buf buf;
6066
6067 freeme = xmlNodeGetContent(prop);
6068 tz = (const char *) freeme;
6069
6070 /* Parse and validate the iCal data */
6071 buf_init_ro_cstr(&buf, tz);
6072 ical = mime->to_object(&buf);
6073 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
6074 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6075 &propstat[PROPSTAT_FORBID],
6076 prop->name, prop->ns, NULL,
6077 CALDAV_VALID_DATA);
6078 *pctx->ret = HTTP_FORBIDDEN;
6079 valid = 0;
6080 }
6081 else if (!icalcomponent_get_first_component(ical,
6082 ICAL_VTIMEZONE_COMPONENT)
6083 || icalcomponent_get_first_real_component(ical)) {
6084 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6085 &propstat[PROPSTAT_FORBID],
6086 prop->name, prop->ns, NULL,
6087 CALDAV_VALID_OBJECT);
6088 *pctx->ret = HTTP_FORBIDDEN;
6089 valid = 0;
6090 }
6091 else if (mime != caldav_mime_types) {
6092 tz = icalcomponent_as_ical_string(ical);
6093 }
6094
6095 if (ical) icalcomponent_free(ical);
6096 buf_free(&buf);
6097 }
6098
6099 if (valid) {
6100 /* Remove CALDAV:calendar-timezone-id */
6101 const char *prop_annot =
6102 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone-id";
6103 annotate_state_t *astate = NULL;
6104 int r;
6105
6106 buf_reset(&pctx->buf);
6107 r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate);
6108 if (!r) r = annotate_state_writemask(astate, prop_annot,
6109 httpd_userid, &pctx->buf);
6110 if (!r) {
6111 /* Set CALDAV:calendar-timezone */
6112 proppatch_todb(prop, set, pctx, propstat, (void *) tz);
6113 }
6114 else {
6115 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
6116 &propstat[PROPSTAT_ERROR],
6117 prop->name, prop->ns, NULL, 0);
6118 *pctx->ret = HTTP_SERVER_ERROR;
6119 }
6120 }
6121
6122 if (freeme) xmlFree(freeme);
6123 if (type) xmlFree(type);
6124 if (ver) xmlFree(ver);
6125 }
6126 else {
6127 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6128 &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
6129
6130 *pctx->ret = HTTP_FORBIDDEN;
6131 }
6132
6133 return 0;
6134 }
6135
6136
6137 /* Callback to prescreen/fetch CALDAV/CS:calendar-availability */
propfind_availability(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6138 static int propfind_availability(const xmlChar *name, xmlNsPtr ns,
6139 struct propfind_ctx *fctx,
6140 xmlNodePtr prop,
6141 xmlNodePtr resp __attribute__((unused)),
6142 struct propstat propstat[],
6143 void *rock __attribute__((unused)))
6144 {
6145 struct buf attrib = BUF_INITIALIZER;
6146 const char *data = NULL;
6147 unsigned long datalen = 0;
6148 int r = 0;
6149
6150 if (propstat) {
6151 buf_reset(&fctx->buf);
6152 buf_printf(&fctx->buf, DAV_ANNOT_NS "<%s>%s",
6153 (const char *) ns->href, name);
6154
6155 if (fctx->mailbox && !fctx->record) {
6156 r = annotatemore_lookupmask(fctx->mailbox->name,
6157 buf_cstring(&fctx->buf),
6158 httpd_userid, &attrib);
6159 }
6160
6161 if (!attrib.len && xmlStrcmp(ns->href, BAD_CAST XML_NS_CALDAV)) {
6162 /* Check for CALDAV:calendar-availability */
6163 buf_reset(&fctx->buf);
6164 buf_printf(&fctx->buf, DAV_ANNOT_NS "<%s>%s", XML_NS_CALDAV, name);
6165
6166 if (fctx->mailbox && !fctx->record) {
6167 r = annotatemore_lookupmask(fctx->mailbox->name,
6168 buf_cstring(&fctx->buf),
6169 httpd_userid, &attrib);
6170 }
6171 }
6172
6173 if (r) r = HTTP_SERVER_ERROR;
6174 else if (!attrib.len) r = HTTP_NOT_FOUND;
6175 else {
6176 data = buf_cstring(&attrib);
6177 datalen = attrib.len;
6178 }
6179 }
6180
6181 if (!r) r = propfind_getdata(name, ns, fctx, prop, propstat,
6182 caldav_mime_types, CALDAV_SUPP_DATA,
6183 data, datalen);
6184 buf_free(&attrib);
6185
6186 return r;
6187 }
6188
6189
6190
6191 /* Callback to write calendar-availability property */
proppatch_availability(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)6192 static int proppatch_availability(xmlNodePtr prop, unsigned set,
6193 struct proppatch_ctx *pctx,
6194 struct propstat propstat[],
6195 void *rock __attribute__((unused)))
6196 {
6197 if (config_allowsched && pctx->txn->req_tgt.flags == TGT_SCHED_INBOX) {
6198 const char *avail = NULL;
6199 xmlChar *type, *ver = NULL, *freeme = NULL;
6200 struct mime_type_t *mime;
6201 unsigned valid = 1;
6202
6203 type = xmlGetProp(prop, BAD_CAST "content-type");
6204 if (type) ver = xmlGetProp(prop, BAD_CAST "version");
6205
6206 /* Check/find requested MIME type */
6207 for (mime = caldav_mime_types; type && mime->content_type; mime++) {
6208 if (is_mediatype(mime->content_type, (const char *) type)) {
6209 if (ver &&
6210 (!mime->version || xmlStrcmp(ver, BAD_CAST mime->version))) {
6211 continue;
6212 }
6213 break;
6214 }
6215 }
6216
6217 if (!mime || !mime->content_type) {
6218 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6219 &propstat[PROPSTAT_FORBID],
6220 prop->name, prop->ns, NULL,
6221 CALDAV_SUPP_DATA);
6222 *pctx->ret = HTTP_FORBIDDEN;
6223 valid = 0;
6224 }
6225 else if (set) {
6226 icalcomponent *ical = NULL;
6227 struct buf buf;
6228
6229 freeme = xmlNodeGetContent(prop);
6230 avail = (const char *) freeme;
6231
6232 /* Parse and validate the iCal data */
6233 buf_init_ro_cstr(&buf, avail);
6234 ical = mime->to_object(&buf);
6235 if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
6236 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6237 &propstat[PROPSTAT_FORBID],
6238 prop->name, prop->ns, NULL,
6239 CALDAV_VALID_DATA);
6240 *pctx->ret = HTTP_FORBIDDEN;
6241 valid = 0;
6242 }
6243 else if (!icalcomponent_get_first_component(ical,
6244 ICAL_VAVAILABILITY_COMPONENT)) {
6245 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6246 &propstat[PROPSTAT_FORBID],
6247 prop->name, prop->ns, NULL,
6248 CALDAV_VALID_OBJECT);
6249 *pctx->ret = HTTP_FORBIDDEN;
6250 valid = 0;
6251 }
6252 else if (mime != caldav_mime_types) {
6253 avail = icalcomponent_as_ical_string(ical);
6254 }
6255
6256 if (ical) icalcomponent_free(ical);
6257 }
6258
6259 if (valid) {
6260 proppatch_todb(prop, set, pctx, propstat, (void *) avail);
6261 }
6262
6263 if (freeme) xmlFree(freeme);
6264 if (type) xmlFree(type);
6265 if (ver) xmlFree(ver);
6266 }
6267 else {
6268 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6269 &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
6270
6271 *pctx->ret = HTTP_FORBIDDEN;
6272 }
6273
6274 return 0;
6275 }
6276
6277
6278 /* Callback to fetch CALDAV:timezone-service-set */
propfind_tzservset(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6279 static int propfind_tzservset(const xmlChar *name, xmlNsPtr ns,
6280 struct propfind_ctx *fctx,
6281 xmlNodePtr prop __attribute__((unused)),
6282 xmlNodePtr resp __attribute__((unused)),
6283 struct propstat propstat[],
6284 void *rock __attribute__((unused)))
6285 {
6286 assert(name && ns && fctx && propstat);
6287
6288 #ifdef HAVE_TZ_BY_REF
6289 if (fctx->req_tgt->resource) return HTTP_NOT_FOUND;
6290
6291 if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
6292 xmlNodePtr node;
6293 const char *proto = NULL, *host = NULL;
6294
6295 node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
6296 name, ns, NULL, 0);
6297
6298 http_proto_host(fctx->txn->req_hdrs, &proto, &host);
6299
6300 buf_reset(&fctx->buf);
6301 buf_printf(&fctx->buf, "%s://%s%s",
6302 proto, host, namespace_tzdist.prefix);
6303
6304 xml_add_href(node, fctx->ns[NS_DAV], buf_cstring(&fctx->buf));
6305
6306 return 0;
6307 }
6308 #endif /* HAVE_TZ_BY_REF */
6309
6310 return HTTP_NOT_FOUND;
6311 }
6312
6313
6314 /* Callback to fetch CALDAV:calendar-timezone-id property */
propfind_tzid(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6315 static int propfind_tzid(const xmlChar *name, xmlNsPtr ns,
6316 struct propfind_ctx *fctx,
6317 xmlNodePtr prop __attribute__((unused)),
6318 xmlNodePtr resp __attribute__((unused)),
6319 struct propstat propstat[],
6320 void *rock __attribute__((unused)))
6321 {
6322 const char *prop_annot =
6323 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone-id";
6324 struct buf attrib = BUF_INITIALIZER;
6325 const char *value = NULL;
6326 int r = 0;
6327
6328 if (!(namespace_calendar.allow & ALLOW_CAL_NOTZ) ||
6329 !fctx->req_tgt->collection || fctx->req_tgt->resource)
6330 return HTTP_NOT_FOUND;
6331
6332 r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
6333 httpd_userid, &attrib);
6334
6335 if (r) r = HTTP_SERVER_ERROR;
6336 else if (attrib.len) {
6337 value = buf_cstring(&attrib);
6338 }
6339 else {
6340 /* Check for CALDAV:calendar-timezone */
6341 prop_annot = DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone";
6342
6343 if (fctx->mailbox && !fctx->record) {
6344 r = annotatemore_lookupmask(fctx->mailbox->name, prop_annot,
6345 httpd_userid, &attrib);
6346 }
6347
6348 if (r) r = HTTP_SERVER_ERROR;
6349 else if (!attrib.len) r = HTTP_NOT_FOUND;
6350 else {
6351 icalcomponent *ical, *vtz;
6352 icalproperty *tzid;
6353
6354 ical = icalparser_parse_string(buf_cstring(&attrib));
6355 vtz = icalcomponent_get_first_component(ical,
6356 ICAL_VTIMEZONE_COMPONENT);
6357 tzid = icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY);
6358 value = icalmemory_tmp_copy(icalproperty_get_tzid(tzid));
6359 icalcomponent_free(ical);
6360 }
6361 }
6362
6363 if (!r) xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
6364 name, ns, BAD_CAST value, 0);
6365
6366 buf_free(&attrib);
6367
6368 return r;
6369 }
6370
6371
6372 /* Callback to write CALDAV:calendar-timezone-id property */
proppatch_tzid(xmlNodePtr prop,unsigned set,struct proppatch_ctx * pctx,struct propstat propstat[],void * rock)6373 static int proppatch_tzid(xmlNodePtr prop, unsigned set,
6374 struct proppatch_ctx *pctx,
6375 struct propstat propstat[],
6376 void *rock __attribute__((unused)))
6377 {
6378 #ifdef HAVE_TZ_BY_REF
6379 if ((namespace_calendar.allow & ALLOW_CAL_NOTZ) &&
6380 pctx->txn->req_tgt.collection && !pctx->txn->req_tgt.resource) {
6381 xmlChar *freeme = NULL;
6382 const char *tzid = NULL;
6383 unsigned valid = 1;
6384 int r;
6385
6386 if (set) {
6387 freeme = xmlNodeGetContent(prop);
6388 tzid = (const char *) freeme;
6389
6390 /* Verify we have tzid record in the database */
6391 r = zoneinfo_lookup(tzid, NULL);
6392 if (r) {
6393 if (r == CYRUSDB_NOTFOUND) {
6394 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6395 &propstat[PROPSTAT_FORBID],
6396 prop->name, prop->ns, NULL,
6397 CALDAV_VALID_TIMEZONE);
6398 }
6399 else {
6400 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
6401 &propstat[PROPSTAT_ERROR],
6402 prop->name, prop->ns, NULL, 0);
6403 }
6404 *pctx->ret = HTTP_FORBIDDEN;
6405 valid = 0;
6406 }
6407 }
6408
6409 if (valid) {
6410 /* Remove CALDAV:calendar-timezone */
6411 const char *prop_annot =
6412 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-timezone";
6413 annotate_state_t *astate = NULL;
6414
6415 buf_reset(&pctx->buf);
6416 r = mailbox_get_annotate_state(pctx->mailbox, 0, &astate);
6417 if (!r) r = annotate_state_writemask(astate, prop_annot,
6418 httpd_userid, &pctx->buf);
6419
6420 if (r) {
6421 xml_add_prop(HTTP_SERVER_ERROR, pctx->ns[NS_DAV],
6422 &propstat[PROPSTAT_ERROR],
6423 prop->name, prop->ns, NULL, 0);
6424 *pctx->ret = HTTP_SERVER_ERROR;
6425 }
6426 else {
6427 /* Set CALDAV:calendar-timezone-id */
6428 proppatch_todb(prop, set, pctx, propstat, (void *) tzid);
6429 }
6430 }
6431
6432 if (freeme) xmlFree(freeme);
6433
6434 return 0;
6435 }
6436 #else
6437 (void) set; /* squash compiler warning */
6438 #endif /* HAVE_TZ_BY_REF */
6439
6440 xml_add_prop(HTTP_FORBIDDEN, pctx->ns[NS_DAV],
6441 &propstat[PROPSTAT_FORBID], prop->name, prop->ns, NULL, 0);
6442
6443 *pctx->ret = HTTP_FORBIDDEN;
6444
6445 return 0;
6446 }
6447
6448
6449 /* Callback to fetch CALDAV:supported-rscale-set */
propfind_rscaleset(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6450 static int propfind_rscaleset(const xmlChar *name, xmlNsPtr ns,
6451 struct propfind_ctx *fctx,
6452 xmlNodePtr prop __attribute__((unused)),
6453 xmlNodePtr resp __attribute__((unused)),
6454 struct propstat propstat[],
6455 void *rock __attribute__((unused)))
6456 {
6457 assert(name && ns && fctx && propstat);
6458
6459 if (fctx->req_tgt->resource) return HTTP_NOT_FOUND;
6460
6461 if (rscale_calendars) {
6462 xmlNodePtr top;
6463 int i, n;
6464
6465 top = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV], &propstat[PROPSTAT_OK],
6466 name, ns, NULL, 0);
6467
6468 for (i = 0, n = rscale_calendars->num_elements; i < n; i++) {
6469 const char **rscale = icalarray_element_at(rscale_calendars, i);
6470
6471 xmlNewChild(top, fctx->ns[NS_CALDAV],
6472 BAD_CAST "supported-rscale", BAD_CAST *rscale);
6473 }
6474
6475 return 0;
6476 }
6477
6478 return HTTP_NOT_FOUND;
6479 }
6480
6481
6482 /* Callback to fetch CS:allowed-sharing-modes */
propfind_sharingmodes(const xmlChar * name,xmlNsPtr ns,struct propfind_ctx * fctx,xmlNodePtr prop,xmlNodePtr resp,struct propstat propstat[],void * rock)6483 static int propfind_sharingmodes(const xmlChar *name, xmlNsPtr ns,
6484 struct propfind_ctx *fctx,
6485 xmlNodePtr prop __attribute__((unused)),
6486 xmlNodePtr resp __attribute__((unused)),
6487 struct propstat propstat[],
6488 void *rock __attribute__((unused)))
6489 {
6490 xmlNodePtr node = xml_add_prop(HTTP_OK, fctx->ns[NS_DAV],
6491 &propstat[PROPSTAT_OK], name, ns, NULL, 0);
6492
6493 fctx->flags.cs_sharing = 1;
6494
6495 if (fctx->req_tgt->collection && !fctx->req_tgt->flags &&
6496 !fctx->req_tgt->resource &&
6497 mboxname_userownsmailbox(fctx->req_tgt->userid, fctx->mbentry->name)) {
6498 xmlNewChild(node, NULL, BAD_CAST "can-be-shared", NULL);
6499 #if 0 /* XXX this is probably iCloud specific */
6500 xmlNewChild(node, NULL, BAD_CAST "can-be-published", NULL);
6501 #endif
6502 }
6503
6504 return 0;
6505 }
6506
6507
report_cal_query(struct transaction_t * txn,struct meth_params * rparams,xmlNodePtr inroot,struct propfind_ctx * fctx)6508 static int report_cal_query(struct transaction_t *txn,
6509 struct meth_params *rparams __attribute__((unused)),
6510 xmlNodePtr inroot, struct propfind_ctx *fctx)
6511 {
6512 int ret = 0;
6513 xmlNodePtr node;
6514 struct calquery_filter calfilter;
6515
6516 memset(&calfilter, 0, sizeof(struct calquery_filter));
6517
6518 fctx->filter_crit = &calfilter;
6519 fctx->open_db = (db_open_proc_t) &caldav_open_mailbox;
6520 fctx->close_db = (db_close_proc_t) &caldav_close;
6521 fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource;
6522 fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach;
6523 fctx->proc_by_resource = &propfind_by_resource;
6524 fctx->davdb = NULL;
6525
6526 /* Parse children element of report */
6527 for (node = inroot->children; node; node = node->next) {
6528 if (node->type == XML_ELEMENT_NODE) {
6529 if (!xmlStrcmp(node->name, BAD_CAST "filter")) {
6530 ret = parse_calfilter(node, &calfilter, &txn->error);
6531 if (ret) goto done;
6532 else fctx->filter = apply_calfilter;
6533 }
6534 else if (!xmlStrcmp(node->name, BAD_CAST "timezone")) {
6535 xmlChar *tzdata = NULL;
6536 icalcomponent *ical = NULL, *tz = NULL;
6537
6538 /* XXX Need to pass this to query for floating time */
6539 syslog(LOG_WARNING, "REPORT calendar-query w/timezone");
6540 tzdata = xmlNodeGetContent(node);
6541 if (tzdata) {
6542 ical = icalparser_parse_string((const char *) tzdata);
6543 xmlFree(tzdata);
6544 }
6545
6546 /* Validate the iCal data */
6547 if (!ical || !icalrestriction_check(ical) ||
6548 icalcomponent_get_first_real_component(ical) ||
6549 !(tz = icalcomponent_get_first_component(ical,
6550 ICAL_VTIMEZONE_COMPONENT))) {
6551 txn->error.precond = CALDAV_VALID_DATA;
6552 ret = HTTP_FORBIDDEN;
6553 }
6554 else {
6555 icalcomponent_remove_component(ical, tz);
6556 calfilter.tz = icaltimezone_new();
6557 icaltimezone_set_component(calfilter.tz, tz);
6558 }
6559
6560 if (ical) icalcomponent_free(ical);
6561 if (ret) return ret;
6562 }
6563 else if (!xmlStrcmp(node->name, BAD_CAST "timezone-id")) {
6564 xmlChar *tzid = NULL;
6565
6566 /* XXX Need to pass this to query for floating time */
6567 syslog(LOG_WARNING, "REPORT calendar-query w/tzid");
6568 tzid = xmlNodeGetContent(node);
6569
6570 if (tzid) {
6571 calfilter.tz =
6572 icaltimezone_get_builtin_timezone_from_tzid((const char *) tzid);
6573 xmlFree(tzid);
6574
6575 if (!calfilter.tz) {
6576 txn->error.precond = CALDAV_VALID_TIMEZONE;
6577 return HTTP_FORBIDDEN;
6578 }
6579 }
6580 }
6581 }
6582 }
6583
6584 if (fctx->depth++ > 0) {
6585 /* Calendar collection(s) */
6586 if (txn->req_tgt.collection) {
6587 /* Add response for target calendar collection */
6588 propfind_by_collection(txn->req_tgt.mbentry, fctx);
6589 }
6590 else {
6591 /* Add responses for all contained calendar collections */
6592 mboxlist_mboxtree(txn->req_tgt.mbentry->name,
6593 propfind_by_collection, fctx,
6594 MBOXTREE_SKIP_ROOT);
6595
6596 /* Add responses for all shared calendar collections */
6597 mboxlist_usersubs(txn->req_tgt.userid,
6598 propfind_by_collection, fctx,
6599 MBOXTREE_SKIP_PERSONAL);
6600 }
6601
6602 ret = *fctx->ret;
6603 }
6604
6605 done:
6606 /* Free filter structure */
6607 if (calfilter.tz) icaltimezone_free(calfilter.tz, 1);
6608 free_compfilter(calfilter.comp);
6609
6610 if (fctx->davdb) {
6611 caldav_close(fctx->davdb);
6612 fctx->davdb = NULL;
6613 }
6614
6615 return (ret ? ret : HTTP_MULTI_STATUS);
6616 }
6617
6618
is_busytime(icalcomponent * comp)6619 static int is_busytime(icalcomponent *comp)
6620 {
6621 /* Check TRANSP and STATUS per RFC 4791, section 7.10 */
6622 const icalproperty *prop;
6623
6624 /* Skip transparent events */
6625 prop = icalcomponent_get_first_property(comp, ICAL_TRANSP_PROPERTY);
6626 if (prop && icalproperty_get_transp(prop) == ICAL_TRANSP_TRANSPARENT)
6627 return 0;
6628
6629 /* Skip cancelled events */
6630 if (icalcomponent_get_status(comp) == ICAL_STATUS_CANCELLED) return 0;
6631
6632 return 1;
6633 }
6634
6635
6636 /* Append a new busytime period to the busytime array */
add_freebusy(struct icaltimetype * recurid,struct icaltimetype * start,struct icaltimetype * end,icalparameter_fbtype fbtype,struct freebusy_filter * fbfilter)6637 static void add_freebusy(struct icaltimetype *recurid,
6638 struct icaltimetype *start,
6639 struct icaltimetype *end,
6640 icalparameter_fbtype fbtype,
6641 struct freebusy_filter *fbfilter)
6642 {
6643 struct freebusy_array *freebusy = &fbfilter->freebusy;
6644 struct freebusy *newfb;
6645
6646 /* Grow the array, if necessary */
6647 if (freebusy->len == freebusy->alloc) {
6648 freebusy->alloc += 100; /* XXX arbitrary */
6649 freebusy->fb = xrealloc(freebusy->fb,
6650 freebusy->alloc * sizeof(struct freebusy));
6651 }
6652
6653 /* Add new freebusy */
6654 newfb = &freebusy->fb[freebusy->len++];
6655 memset(newfb, 0, sizeof(struct freebusy));
6656
6657 if (recurid) newfb->recurid = *recurid;
6658
6659 if (icaltime_is_date(*start)) {
6660 newfb->per.duration = icaltime_subtract(*end, *start);
6661 newfb->per.end = icaltime_null_time();
6662 start->is_date = 0; /* MUST be DATE-TIME */
6663 newfb->per.start = icaltime_convert_to_zone(*start, utc_zone);
6664 }
6665 else {
6666 newfb->per.duration = icaldurationtype_null_duration();
6667 if (icaltime_compare(fbfilter->end, *end) < 0) {
6668 newfb->per.end = fbfilter->end;
6669 }
6670 else newfb->per.end = *end;
6671
6672 if (icaltime_compare(fbfilter->start, *start) > 0) {
6673 newfb->per.start = fbfilter->start;
6674 }
6675 else newfb->per.start = *start;
6676 }
6677 newfb->type = fbtype;
6678 }
6679
6680
6681 /* Append a new busytime period for recurring comp to the busytime array */
add_freebusy_comp(icalcomponent * comp,icaltimetype start,icaltimetype end,void * rock)6682 static int add_freebusy_comp(icalcomponent *comp,
6683 icaltimetype start, icaltimetype end,
6684 void *rock)
6685 {
6686 struct freebusy_filter *fbfilter = (struct freebusy_filter *) rock;
6687 struct icaltimetype recurid;
6688 icalparameter_fbtype fbtype;
6689
6690 if (!is_busytime(comp)) return 1;
6691
6692 /* Set start and end times */
6693 start = icaltime_convert_to_zone(start, utc_zone);
6694 end = icaltime_convert_to_zone(end, utc_zone);
6695
6696 /* Set recurid */
6697 recurid = icalcomponent_get_recurrenceid_with_zone(comp);
6698 if (icaltime_is_null_time(recurid)) recurid = start;
6699 else {
6700 recurid = icaltime_convert_to_zone(recurid, utc_zone);
6701 recurid.is_date = 0; /* make DATE-TIME for comparison */
6702 }
6703
6704 /* Set FBTYPE */
6705 switch (icalcomponent_isa(comp)) {
6706 case ICAL_VEVENT_COMPONENT:
6707 fbtype = icalcomponent_get_status(comp) == ICAL_STATUS_TENTATIVE ?
6708 ICAL_FBTYPE_BUSYTENTATIVE : ICAL_FBTYPE_BUSY;
6709 break;
6710
6711 case ICAL_VFREEBUSY_COMPONENT:
6712 /* XXX Need to do something better here */
6713 fbtype = ICAL_FBTYPE_BUSY;
6714 break;
6715
6716 #ifdef HAVE_VAVAILABILITY
6717 case ICAL_VAVAILABILITY_COMPONENT: {
6718 enum icalproperty_busytype busytype = ICAL_BUSYTYPE_BUSYUNAVAILABLE;
6719 icalproperty *prop =
6720 icalcomponent_get_first_property(comp, ICAL_BUSYTYPE_PROPERTY);
6721
6722 if (prop) busytype = icalproperty_get_busytype(prop);
6723
6724 switch (busytype) {
6725 case ICAL_BUSYTYPE_BUSYUNAVAILABLE:
6726 fbtype = ICAL_FBTYPE_BUSYUNAVAILABLE;
6727 break;
6728 case ICAL_BUSYTYPE_BUSYTENTATIVE:
6729 fbtype = ICAL_FBTYPE_BUSYTENTATIVE;
6730 break;
6731 default:
6732 fbtype = ICAL_FBTYPE_BUSY;
6733 break;
6734 }
6735 break;
6736 }
6737
6738 case ICAL_XAVAILABLE_COMPONENT:
6739 fbtype = ICAL_FBTYPE_FREE;
6740 break;
6741 #endif /* HAVE_VAVAILABILITY */
6742
6743 default:
6744 fbtype = ICAL_FBTYPE_NONE;
6745 break;
6746 }
6747
6748 add_freebusy(&recurid, &start, &end, fbtype, fbfilter);
6749
6750 return 1;
6751 }
6752
6753
expand_occurrences(icalcomponent * ical,struct freebusy_filter * fbfilter)6754 static void expand_occurrences(icalcomponent *ical,
6755 struct freebusy_filter *fbfilter)
6756 {
6757 /* Create a span for the given time-range */
6758 struct icalperiodtype rangespan =
6759 { fbfilter->start, fbfilter->end, icaldurationtype_null_duration() };
6760
6761 icalcomponent_myforeach(ical, rangespan, NULL, add_freebusy_comp, fbfilter);
6762 }
6763
6764
6765 /* Append a new vavailability period to the vavail array */
6766 static void
add_vavailability(struct vavailability_array * vavail,icalcomponent * ical)6767 add_vavailability(struct vavailability_array *vavail, icalcomponent *ical)
6768 {
6769 struct vavailability *newav;
6770 icalcomponent *vav;
6771 icalproperty *prop;
6772
6773 /* Grow the array, if necessary */
6774 if (vavail->len == vavail->alloc) {
6775 vavail->alloc += 10; /* XXX arbitrary */
6776 vavail->vav = xrealloc(vavail->vav,
6777 vavail->alloc * sizeof(struct vavailability));
6778 }
6779
6780 /* Add new vavailability */
6781 newav = &vavail->vav[vavail->len++];
6782 newav->ical = ical;
6783
6784 vav = icalcomponent_get_first_real_component(ical);
6785
6786 /* Set period */
6787 newav->per.start = icalcomponent_get_dtstart(vav);
6788 if (icaltime_is_null_time(newav->per.start)) {
6789 newav->per.start =
6790 icaltime_from_timet_with_zone(caldav_epoch, 0, utc_zone);
6791 }
6792 else {
6793 newav->per.start = icaltime_convert_to_zone(newav->per.start, utc_zone);
6794 }
6795 newav->per.end = icalcomponent_get_dtend(vav);
6796 if (icaltime_is_null_time(newav->per.end)) {
6797 newav->per.end =
6798 icaltime_from_timet_with_zone(caldav_eternity, 0, utc_zone);
6799 }
6800 else {
6801 newav->per.end = icaltime_convert_to_zone(newav->per.end, utc_zone);
6802 }
6803 newav->per.duration = icaldurationtype_null_duration();
6804
6805 /* Set PRIORITY - 0 (or none) has lower priority than 9 */
6806 prop = icalcomponent_get_first_property(vav, ICAL_PRIORITY_PROPERTY);
6807 if (prop) newav->priority = icalproperty_get_priority(prop);
6808 if (!prop || !newav->priority) newav->priority = 10;
6809 }
6810
6811
6812 /* caldav_foreach() callback to find busytime of a resource */
busytime_by_resource(void * rock,void * data)6813 static int busytime_by_resource(void *rock, void *data)
6814 {
6815 struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
6816 struct caldav_data *cdata = (struct caldav_data *) data;
6817 struct freebusy_filter *fbfilter =
6818 (struct freebusy_filter *) fctx->filter_crit;
6819
6820 keepalive_response(fctx->txn);
6821
6822 if (!cdata->dav.imap_uid) return 0;
6823
6824 /* Perform component filtering */
6825 if (!(cdata->comp_type &
6826 (CAL_COMP_VEVENT | CAL_COMP_VFREEBUSY | CAL_COMP_VAVAILABILITY))) {
6827 return 0;
6828 }
6829
6830 /* Perform time-range filtering */
6831 struct icaltimetype dtstart = icaltime_from_string(cdata->dtstart);
6832 struct icaltimetype dtend = icaltime_from_string(cdata->dtend);
6833
6834 if (icaltime_compare(dtend, fbfilter->start) <= 0) {
6835 /* Component ends earlier than range */
6836 return 0;
6837 }
6838 if (icaltime_compare(dtstart, fbfilter->end) >= 0) {
6839 /* Component starts later than range */
6840 return 0;
6841 }
6842
6843 if (cdata->comp_flags.recurring ||
6844 cdata->comp_type == CAL_COMP_VAVAILABILITY) {
6845 /* Need to mmap() and parse iCalendar object */
6846 struct index_record record;
6847 icalcomponent *ical = NULL;
6848 int r;
6849
6850 /* Fetch index record for the resource */
6851 r = mailbox_find_index_record(fctx->mailbox,
6852 cdata->dav.imap_uid, &record);
6853
6854 if (!r) ical = record_to_ical(fctx->mailbox, &record, NULL);
6855 if (!ical) return 0;
6856
6857 if (cdata->comp_flags.recurring) {
6858 /* Component is recurring - process each recurrence */
6859 expand_occurrences(ical, fbfilter);
6860
6861 icalcomponent_free(ical);
6862 }
6863 else {
6864 /* VAVAILABILITY - add to our array for later use */
6865 add_vavailability(&fbfilter->vavail, ical);
6866 }
6867 }
6868 else {
6869 /* Component is non-recurring */
6870 icalparameter_fbtype fbtype;
6871
6872 if (cdata->comp_flags.transp) {
6873 /* Don't include transparent events in freebusy */
6874 return 0;
6875 }
6876 if (cdata->comp_flags.status == CAL_STATUS_CANCELED) {
6877 /* Don't include canceled events in freebusy */
6878 return 0;
6879 }
6880
6881 switch (cdata->comp_flags.status) {
6882 case CAL_STATUS_UNAVAILABLE:
6883 fbtype = ICAL_FBTYPE_BUSYUNAVAILABLE; break;
6884
6885 case CAL_STATUS_TENTATIVE:
6886 fbtype = ICAL_FBTYPE_BUSYTENTATIVE; break;
6887
6888 default:
6889 fbtype = ICAL_FBTYPE_BUSY; break;
6890 }
6891
6892 add_freebusy(&dtstart, &dtstart, &dtend, fbtype, fbfilter);
6893 }
6894
6895 return 0;
6896 }
6897
6898
6899 /* mboxlist_findall() callback to find busytime of a collection */
busytime_by_collection(const mbentry_t * mbentry,void * rock)6900 static int busytime_by_collection(const mbentry_t *mbentry, void *rock)
6901 {
6902 const char *mboxname = mbentry->name;
6903 struct propfind_ctx *fctx = (struct propfind_ctx *) rock;
6904 struct freebusy_filter *fbfilter =
6905 (struct freebusy_filter *) fctx->filter_crit;
6906
6907 if (fbfilter->flags & CHECK_CAL_TRANSP) {
6908 /* Check if the collection is marked as transparent */
6909 struct buf attrib = BUF_INITIALIZER;
6910 const char *prop_annot =
6911 DAV_ANNOT_NS "<" XML_NS_CALDAV ">schedule-calendar-transp";
6912
6913 if (!annotatemore_lookupmask(mboxname, prop_annot,
6914 httpd_userid, &attrib)) {
6915 if (!strcmp(buf_cstring(&attrib), "transparent")) {
6916 buf_free(&attrib);
6917 return 0;
6918 }
6919 buf_free(&attrib);
6920 }
6921 }
6922
6923 return propfind_by_collection(mbentry, rock);
6924 }
6925
6926
6927 /* Compare start/end times of freebusy periods -- used for sorting */
compare_freebusy(const void * fb1,const void * fb2)6928 static int compare_freebusy(const void *fb1, const void *fb2)
6929 {
6930 struct freebusy *a = (struct freebusy *) fb1;
6931 struct freebusy *b = (struct freebusy *) fb2;
6932
6933 int r = icaltime_compare(a->per.start, b->per.start);
6934
6935 if (r == 0) r = icaltime_compare(a->per.end, b->per.end);
6936
6937 return r;
6938 }
6939
6940
6941 /* Compare type and start/end times of freebusy periods -- used for sorting */
compare_freebusy_with_type(const void * fb1,const void * fb2)6942 static int compare_freebusy_with_type(const void *fb1, const void *fb2)
6943 {
6944 struct freebusy *a = (struct freebusy *) fb1;
6945 struct freebusy *b = (struct freebusy *) fb2;
6946
6947 int r = a->type - b->type;
6948
6949 if (r == 0) r = compare_freebusy(fb1, fb2);
6950
6951 return r;
6952 }
6953
6954
6955 /* Compare priority, start/end times, and type of vavail periods for sorting */
compare_vavail(const void * v1,const void * v2)6956 static int compare_vavail(const void *v1, const void *v2)
6957 {
6958 struct vavailability *a = (struct vavailability *) v1;
6959 struct vavailability *b = (struct vavailability *) v2;
6960
6961 int r = a->priority - b->priority;
6962
6963 if (r == 0) {
6964 r = icaltime_compare(a->per.start, b->per.start);
6965
6966 if (r == 0) r = icaltime_compare(a->per.end, b->per.end);
6967 }
6968
6969 return r;
6970 }
6971
6972
combine_vavailability(struct freebusy_filter * fbfilter)6973 static void combine_vavailability(struct freebusy_filter *fbfilter)
6974 {
6975 struct vavailability_array *vavail = &fbfilter->vavail;
6976 struct freebusy_filter availfilter;
6977 struct query_range {
6978 struct icalperiodtype per;
6979 struct query_range *next;
6980 } *ranges, *range, *prev, *next;
6981 unsigned i, j;
6982
6983 memset(&availfilter, 0, sizeof(struct freebusy_filter));
6984
6985 /* Sort VAVAILBILITY periods by priority and start time */
6986 qsort(vavail->vav, vavail->len,
6987 sizeof(struct vavailability), compare_vavail);
6988
6989 /* Maintain a linked list of remaining time ranges
6990 * to be filled in by lower priority VAV components
6991 * starting with the time range in the freebusy query.
6992 * Ranges are removed, clipped, or split as they get filled.
6993 * Quit when no ranges or VAVAILABILITY components remain.
6994 */
6995 ranges = xmalloc(sizeof(struct query_range));
6996 ranges->per.start = fbfilter->start;
6997 ranges->per.end = fbfilter->end;
6998 ranges->next = NULL;
6999
7000 for (i = 0; i < vavail->len; i++) {
7001 struct vavailability *vav = &vavail->vav[i];
7002 icalcomponent *comp;
7003
7004 comp = icalcomponent_get_first_component(vav->ical,
7005 ICAL_VAVAILABILITY_COMPONENT);
7006
7007 for (range = ranges, prev = NULL; range; prev = range, range = next) {
7008 struct icalperiodtype period;
7009
7010 next = range->next;
7011
7012 if (icaltime_compare(vav->per.end, range->per.start) <= 0 ||
7013 icaltime_compare(vav->per.start, range->per.end) >= 0) {
7014 /* Skip VAVAILABILITY outside our range */
7015 continue;
7016 }
7017
7018 /* Set filter range (maximum start time and minimum end time)
7019 and adjust current range as necessary */
7020 if (icaltime_compare(vav->per.start, range->per.start) <= 0) {
7021 /* VAV starts before range - filter using range start */
7022 availfilter.start = range->per.start;
7023
7024 if (icaltime_compare(vav->per.end, range->per.end) >= 0) {
7025 /* VAV ends after range - filter using range end */
7026 availfilter.end = range->per.end;
7027
7028 /* Filling entire range - remove it */
7029 if (prev) prev->next = next;
7030 else ranges = NULL;
7031
7032 free(range);
7033 range = NULL;
7034 }
7035 else {
7036 /* VAV ends before range - filter using VAV end */
7037 availfilter.end = vav->per.end;
7038
7039 /* Filling front part - adjust start to back remainder */
7040 range->per.start = vav->per.end;
7041 }
7042 }
7043 else {
7044 /* VAV starts after range - filter using VAV start */
7045 availfilter.start = vav->per.start;
7046
7047 if (icaltime_compare(vav->per.end, range->per.end) >= 0) {
7048 /* VAV ends after range - filter using range end */
7049 availfilter.end = range->per.end;
7050 }
7051 else {
7052 /* VAV ends before range - filter using VAV end */
7053 availfilter.end = vav->per.end;
7054
7055 /* Splitting range - insert new range for back remainder */
7056 struct query_range *newr =
7057 xmalloc(sizeof(struct query_range));
7058 newr->per.start = vav->per.end;
7059 newr->per.end = range->per.end;
7060 newr->next = next;
7061 range->next = newr;
7062 }
7063
7064 /* Adjust end to front remainder */
7065 range->per.end = vav->per.start;
7066 }
7067
7068 /* Expand available time occurrences */
7069 expand_occurrences(comp, &availfilter);
7070
7071 /* Calculate unavailable periods and add to busytime */
7072 period.start = availfilter.start;
7073 for (j = 0; j < availfilter.freebusy.len; j++) {
7074 struct freebusy *fb = &availfilter.freebusy.fb[j];
7075
7076 /* Ignore overridden instances */
7077 if (fb->type == ICAL_FBTYPE_NONE) continue;
7078
7079 period.end = fb->per.start;
7080 if (icaltime_compare(period.end, period.start) > 0) {
7081 add_freebusy_comp(comp, period.start, period.end, fbfilter);
7082 }
7083 period.start = fb->per.end;
7084 }
7085 period.end = availfilter.end;
7086 if (icaltime_compare(period.end, period.start) > 0) {
7087 add_freebusy_comp(comp, period.start, period.end, fbfilter);
7088 }
7089 }
7090
7091 /* Done with this ical component */
7092 icalcomponent_free(vav->ical);
7093 }
7094
7095 /* Cleanup the vavailability array */
7096 free(vavail->vav);
7097
7098 /* Cleanup the availability array */
7099 if (availfilter.freebusy.fb) free(availfilter.freebusy.fb);
7100
7101 /* Remove any unfilled ranges */
7102 for (; ranges; ranges = next) {
7103 next = ranges->next;
7104 free(ranges);
7105 }
7106 }
7107
7108
7109 /* Create an iCalendar object containing busytime of all specified resources */
busytime_query_local(struct transaction_t * txn,struct propfind_ctx * fctx,char mailboxname[],icalproperty_method method,const char * uid,const char * organizer,const char * attendee)7110 icalcomponent *busytime_query_local(struct transaction_t *txn,
7111 struct propfind_ctx *fctx,
7112 char mailboxname[],
7113 icalproperty_method method,
7114 const char *uid,
7115 const char *organizer,
7116 const char *attendee)
7117 {
7118 struct freebusy_filter *fbfilter =
7119 (struct freebusy_filter *) fctx->filter_crit;
7120 struct freebusy_array *freebusy = &fbfilter->freebusy;
7121 struct vavailability_array *vavail = &fbfilter->vavail;
7122 icalcomponent *ical = NULL;
7123 icalcomponent *fbcomp;
7124 icalproperty *prop;
7125 unsigned n;
7126
7127 fctx->open_db = (db_open_proc_t) &caldav_open_mailbox;
7128 fctx->close_db = (db_close_proc_t) &caldav_close;
7129 fctx->lookup_resource = (db_lookup_proc_t) &caldav_lookup_resource;
7130 fctx->foreach_resource = (db_foreach_proc_t) &caldav_foreach;
7131 fctx->proc_by_resource = &busytime_by_resource;
7132
7133 /* Gather up all of the busytime and VAVAILABILITY periods */
7134 if (fctx->depth > 0) {
7135 /* Calendar collection(s) */
7136
7137 if (txn->req_tgt.collection) {
7138 /* Get busytime for target calendar collection */
7139 busytime_by_collection(txn->req_tgt.mbentry, fctx);
7140 }
7141 else {
7142 /* Get busytime for all contained calendar collections */
7143 mboxlist_mboxtree(mailboxname, busytime_by_collection,
7144 fctx, MBOXTREE_SKIP_ROOT);
7145
7146 /* XXX Get busytime for all shared calendar collections? */
7147 }
7148
7149 if (fctx->davdb) caldav_close(fctx->davdb);
7150 }
7151
7152 if (*fctx->ret) return NULL;
7153
7154 if (fbfilter->flags & CHECK_USER_AVAIL) {
7155 /* Check for CALDAV:calendar-availability on user's Inbox */
7156 struct buf attrib = BUF_INITIALIZER;
7157 const char *prop_annot =
7158 DAV_ANNOT_NS "<" XML_NS_CALDAV ">calendar-availability";
7159 char *userid = mboxname_to_userid(mailboxname);
7160 const char *mboxname = caldav_mboxname(userid, SCHED_INBOX);
7161 if (!annotatemore_lookupmask(mboxname, prop_annot,
7162 httpd_userid, &attrib) && attrib.len) {
7163 add_vavailability(vavail,
7164 icalparser_parse_string(buf_cstring(&attrib)));
7165 }
7166 free(userid);
7167 }
7168
7169 /* Combine VAVAILABILITY components into busytime */
7170 if (vavail->len) combine_vavailability(fbfilter);
7171
7172 /* Sort busytime periods by type and start/end times for coalescing */
7173 qsort(freebusy->fb, freebusy->len,
7174 sizeof(struct freebusy), compare_freebusy_with_type);
7175
7176 /* Coalesce busytime periods of same type into one */
7177 for (n = 0; n + 1 < freebusy->len; n++) {
7178 struct freebusy *fb, *next_fb;
7179 icaltimetype end, next_end;
7180 int isdur, next_isdur;
7181
7182 fb = &freebusy->fb[n];
7183 next_fb = &freebusy->fb[n+1];
7184
7185 /* Ignore overridden instances */
7186 if (fb->type == ICAL_FBTYPE_NONE) continue;
7187
7188 isdur = !icaldurationtype_is_null_duration(fb->per.duration);
7189 end = !isdur ? fb->per.end :
7190 icaltime_add(fb->per.start, fb->per.duration);
7191
7192 /* Skip periods of different type or that don't overlap */
7193 if ((fb->type != next_fb->type) ||
7194 icaltime_compare(end, next_fb->per.start) < 0) continue;
7195
7196 /* Coalesce into next busytime */
7197 next_isdur = !icaldurationtype_is_null_duration(next_fb->per.duration);
7198 next_end = !next_isdur ? next_fb->per.end :
7199 icaltime_add(next_fb->per.start, next_fb->per.duration);
7200
7201 if (icaltime_compare(end, next_end) >= 0) {
7202 /* Current period subsumes next */
7203 next_fb->per.end = fb->per.end;
7204 next_fb->per.duration = fb->per.duration;
7205 }
7206 else if (isdur && next_isdur) {
7207 /* Both periods are durations */
7208 struct icaldurationtype overlap =
7209 icaltime_subtract(end, next_fb->per.start);
7210
7211 next_fb->per.duration.days += fb->per.duration.days - overlap.days;
7212 }
7213 else {
7214 /* Need to use explicit period */
7215 next_fb->per.end = next_end;
7216 next_fb->per.duration = icaldurationtype_null_duration();
7217 }
7218
7219 next_fb->per.start = fb->per.start;
7220
7221 /* "Remove" the instance
7222 by setting fbtype to NONE (we ignore these later) */
7223 fb->type = ICAL_FBTYPE_NONE;
7224 }
7225
7226 /* Sort busytime periods by start/end times for addition to VFREEBUSY */
7227 qsort(freebusy->fb, freebusy->len,
7228 sizeof(struct freebusy), compare_freebusy);
7229
7230 /* Construct iCalendar object with VFREEBUSY component */
7231 ical = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
7232 icalproperty_new_version("2.0"),
7233 icalproperty_new_prodid(ical_prodid),
7234 0);
7235
7236 if (method) icalcomponent_set_method(ical, method);
7237
7238 fbcomp = icalcomponent_vanew(ICAL_VFREEBUSY_COMPONENT,
7239 icalproperty_new_dtstamp(
7240 icaltime_from_timet_with_zone(
7241 time(0), 0, utc_zone)),
7242 icalproperty_new_dtstart(fbfilter->start),
7243 icalproperty_new_dtend(fbfilter->end),
7244 0);
7245
7246 icalcomponent_add_component(ical, fbcomp);
7247
7248 if (uid) icalcomponent_set_uid(fbcomp, uid);
7249 if (organizer) {
7250 prop = icalproperty_new_organizer(organizer);
7251 icalcomponent_add_property(fbcomp, prop);
7252 }
7253 if (attendee) {
7254 prop = icalproperty_new_attendee(attendee);
7255 icalcomponent_add_property(fbcomp, prop);
7256 }
7257
7258 /* Add busytime periods to VFREEBUSY component */
7259 for (n = 0; n < freebusy->len; n++) {
7260 struct freebusy *fb = &freebusy->fb[n];
7261 icalproperty *busy;
7262
7263 /* Ignore overridden instances */
7264 if (fb->type == ICAL_FBTYPE_NONE) continue;
7265
7266 /* Create new FREEBUSY property with FBTYPE and add to component */
7267 busy = icalproperty_new_freebusy(fb->per);
7268 icalproperty_add_parameter(busy, icalparameter_new_fbtype(fb->type));
7269 icalcomponent_add_property(fbcomp, busy);
7270 }
7271
7272 return ical;
7273 }
7274
7275
report_fb_query(struct transaction_t * txn,struct meth_params * rparams,xmlNodePtr inroot,struct propfind_ctx * fctx)7276 static int report_fb_query(struct transaction_t *txn,
7277 struct meth_params *rparams __attribute__((unused)),
7278 xmlNodePtr inroot, struct propfind_ctx *fctx)
7279 {
7280 int ret = 0;
7281 const char **hdr;
7282 struct mime_type_t *mime;
7283 struct freebusy_filter fbfilter;
7284 xmlNodePtr node;
7285 icalcomponent *cal;
7286
7287 /* Can not be run against a resource */
7288 if (txn->req_tgt.resource) return HTTP_FORBIDDEN;
7289
7290 /* Check requested MIME type:
7291 1st entry in caldav_mime_types array MUST be default MIME type */
7292 if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
7293 mime = get_accept_type(hdr, caldav_mime_types);
7294 else mime = caldav_mime_types;
7295 if (!mime) return HTTP_NOT_ACCEPTABLE;
7296
7297 memset(&fbfilter, 0, sizeof(struct freebusy_filter));
7298 fbfilter.start = icaltime_from_timet_with_zone(caldav_epoch, 0, utc_zone);
7299 fbfilter.end = icaltime_from_timet_with_zone(caldav_eternity, 0, utc_zone);
7300 fctx->filter_crit = &fbfilter;
7301
7302 /* Parse children element of report */
7303 for (node = inroot->children; node; node = node->next) {
7304 if (node->type == XML_ELEMENT_NODE) {
7305 if (!xmlStrcmp(node->name, BAD_CAST "time-range")) {
7306 xmlChar *start, *end;
7307
7308 start = xmlGetProp(node, BAD_CAST "start");
7309 if (start) {
7310 fbfilter.start = icaltime_from_string((char *) start);
7311 xmlFree(start);
7312 }
7313
7314 end = xmlGetProp(node, BAD_CAST "end");
7315 if (end) {
7316 fbfilter.end = icaltime_from_string((char *) end);
7317 xmlFree(end);
7318 }
7319
7320 if (!is_valid_timerange(fbfilter.start, fbfilter.end)) {
7321 return HTTP_BAD_REQUEST;
7322 }
7323 }
7324 }
7325 }
7326
7327 fctx->depth++;
7328 cal = busytime_query_local(txn, fctx, txn->req_tgt.mbentry->name,
7329 0, NULL, NULL, NULL);
7330
7331 if (fbfilter.freebusy.fb) free(fbfilter.freebusy.fb);
7332
7333 if (cal) {
7334 /* Output the iCalendar object as text/calendar */
7335 struct buf *cal_str = mime->from_object(cal);
7336 icalcomponent_free(cal);
7337
7338 txn->resp_body.type = mime->content_type;
7339
7340 /* iCalendar data in response should not be transformed */
7341 txn->flags.cc |= CC_NOTRANSFORM;
7342
7343 write_body(HTTP_OK, txn, buf_base(cal_str), buf_len(cal_str));
7344 buf_destroy(cal_str);
7345 }
7346 else ret = HTTP_NOT_FOUND;
7347
7348 return ret;
7349 }
7350
7351
7352 /* Replace TZID aliases with the actual TZIDs */
replace_tzid_aliases(icalcomponent * ical,struct hash_table * tzid_table)7353 static void replace_tzid_aliases(icalcomponent *ical,
7354 struct hash_table *tzid_table)
7355 {
7356 icalproperty *prop;
7357 for (prop = icalcomponent_get_first_property(ical, ICAL_ANY_PROPERTY);
7358 prop;
7359 prop = icalcomponent_get_next_property(ical, ICAL_ANY_PROPERTY)) {
7360 icalparameter *param =
7361 icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER);
7362 if (!param) continue;
7363
7364 const char *tzid =
7365 hash_lookup(icalparameter_get_tzid(param), tzid_table);
7366 if (tzid) icalparameter_set_tzid(param, tzid);
7367 }
7368
7369 icalcomponent *comp;
7370 for (comp = icalcomponent_get_first_component(ical, ICAL_ANY_COMPONENT);
7371 comp;
7372 comp = icalcomponent_get_next_component(ical, ICAL_ANY_COMPONENT)) {
7373 replace_tzid_aliases(comp, tzid_table);
7374 }
7375 }
7376
7377
7378 /* Strip all VTIMEZONE components for known TZIDs */
strip_vtimezones(icalcomponent * ical)7379 static void strip_vtimezones(icalcomponent *ical)
7380 {
7381 struct hash_table tzid_table;
7382 icalcomponent *vtz, *next;
7383
7384 /* Create hash table for TZID aliases */
7385 construct_hash_table(&tzid_table, 10, 1);
7386
7387 for (vtz = icalcomponent_get_first_component(ical, ICAL_VTIMEZONE_COMPONENT);
7388 vtz; vtz = next) {
7389
7390 next = icalcomponent_get_next_component(ical, ICAL_VTIMEZONE_COMPONENT);
7391
7392 icalproperty *prop =
7393 icalcomponent_get_first_property(vtz, ICAL_TZID_PROPERTY);
7394 const char *tzid = icalproperty_get_tzid(prop);
7395 struct zoneinfo zi;
7396
7397 if (!zoneinfo_lookup(tzid, &zi)) {
7398 if (zi.type == ZI_LINK) {
7399 /* Add this alias to our table */
7400 hash_insert(tzid, xstrdup(zi.data->s), &tzid_table);
7401 }
7402 freestrlist(zi.data);
7403
7404 icalcomponent_remove_component(ical, vtz);
7405 icalcomponent_free(vtz);
7406 }
7407 }
7408
7409 if (hash_numrecords(&tzid_table)) {
7410 /* Replace all TZID aliases with actual TZIDs.
7411 Note: This NEEDS to be done, otherwise looking up the
7412 builtin timezone will fail on a TZID mismatch. */
7413 replace_tzid_aliases(ical, &tzid_table);
7414 }
7415 free_hash_table(&tzid_table, free);
7416 }
7417
7418
7419 /* Store the iCal data in the specified calendar/resource */
caldav_store_resource(struct transaction_t * txn,icalcomponent * ical,struct mailbox * mailbox,const char * resource,struct caldav_db * caldavdb,unsigned flags,const char * schedule_address)7420 int caldav_store_resource(struct transaction_t *txn, icalcomponent *ical,
7421 struct mailbox *mailbox, const char *resource,
7422 struct caldav_db *caldavdb, unsigned flags,
7423 const char *schedule_address)
7424 {
7425 int ret;
7426 icalcomponent *comp;
7427 icalcomponent_kind kind;
7428 icalproperty_method meth;
7429 icalproperty *prop;
7430 unsigned mykind = 0, tzbyref = 0;
7431 const char *organizer = NULL;
7432 const char *prop_annot =
7433 DAV_ANNOT_NS "<" XML_NS_CALDAV ">supported-calendar-component-set";
7434 struct buf attrib = BUF_INITIALIZER;
7435 struct caldav_data *cdata;
7436 const char *uid;
7437 struct index_record *oldrecord = NULL, record;
7438 char datestr[80], *mimehdr;
7439 const char *sched_tag;
7440 strarray_t imapflags = STRARRAY_INITIALIZER;
7441
7442 /* Check for supported component type */
7443 comp = icalcomponent_get_first_real_component(ical);
7444 uid = icalcomponent_get_uid(comp);
7445 kind = icalcomponent_isa(comp);
7446 switch (kind) {
7447 case ICAL_VEVENT_COMPONENT: mykind = CAL_COMP_VEVENT; break;
7448 case ICAL_VTODO_COMPONENT: mykind = CAL_COMP_VTODO; break;
7449 case ICAL_VJOURNAL_COMPONENT: mykind = CAL_COMP_VJOURNAL; break;
7450 case ICAL_VFREEBUSY_COMPONENT: mykind = CAL_COMP_VFREEBUSY; break;
7451 case ICAL_VAVAILABILITY_COMPONENT: mykind = CAL_COMP_VAVAILABILITY; break;
7452 #ifdef HAVE_VPOLL
7453 case ICAL_VPOLL_COMPONENT: mykind = CAL_COMP_VPOLL; break;
7454 #endif
7455 default:
7456 txn->error.precond = CALDAV_SUPP_COMP;
7457 return HTTP_FORBIDDEN;
7458 }
7459
7460 if (!annotatemore_lookupmask(mailbox->name,
7461 prop_annot, httpd_userid, &attrib)
7462 && attrib.len) {
7463 unsigned long supp_comp = strtoul(buf_cstring(&attrib), NULL, 10);
7464
7465 buf_free(&attrib);
7466
7467 if (!(mykind & supp_comp)) {
7468 txn->error.precond = CALDAV_SUPP_COMP;
7469 return HTTP_FORBIDDEN;
7470 }
7471 }
7472
7473 /* Find message UID for the resource, if exists */
7474 caldav_lookup_resource(caldavdb, mailbox->name, resource, &cdata, 0);
7475
7476 /* does it already exist? */
7477 if (cdata->dav.imap_uid) {
7478 /* Check for change of iCalendar UID */
7479 if (strcmp(cdata->ical_uid, uid)) {
7480 /* CALDAV:no-uid-conflict */
7481 txn->error.precond = CALDAV_UID_CONFLICT;
7482 return HTTP_FORBIDDEN;
7483 }
7484 /* Fetch index record for the resource */
7485 oldrecord = &record;
7486 mailbox_find_index_record(mailbox, cdata->dav.imap_uid, oldrecord);
7487 }
7488
7489 /* Remove all X-LIC-ERROR properties */
7490 icalcomponent_strip_errors(ical);
7491
7492 /* Remove all VTIMEZONE components for known TZIDs */
7493 if (namespace_calendar.allow & ALLOW_CAL_NOTZ) {
7494 strip_vtimezones(ical);
7495 tzbyref = 1;
7496 }
7497
7498 /* If we are just stripping VTIMEZONEs from resource, flag it */
7499 if (flags & TZ_STRIP) strarray_append(&imapflags, DFLAG_UNCHANGED);
7500
7501 /* Create and cache RFC 5322 header fields for resource */
7502 prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
7503 if (prop) {
7504 organizer = icalproperty_get_organizer(prop);
7505 if (organizer) {
7506 if (!strncasecmp(organizer, "mailto:", 7)) organizer += 7;
7507 assert(!buf_len(&txn->buf));
7508 buf_printf(&txn->buf, "<%s>", organizer);
7509 mimehdr = charset_encode_mimeheader(buf_cstring(&txn->buf),
7510 buf_len(&txn->buf));
7511 spool_replace_header(xstrdup("From"), mimehdr, txn->req_hdrs);
7512 buf_reset(&txn->buf);
7513 }
7514 }
7515
7516 /* Set Schedule-Tag, if any */
7517 if (flags & NEW_STAG) {
7518 if (oldrecord) sched_tag = message_guid_encode(&oldrecord->guid);
7519 else sched_tag = NULL_ETAG;
7520 }
7521 else if (organizer) sched_tag = cdata->sched_tag;
7522 else sched_tag = cdata->sched_tag = NULL;
7523
7524 prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY);
7525 if (prop) {
7526 mimehdr = charset_encode_mimeheader(icalproperty_get_summary(prop), 0);
7527 spool_replace_header(xstrdup("Subject"), mimehdr, txn->req_hdrs);
7528 }
7529 else spool_replace_header(xstrdup("Subject"),
7530 xstrdup(icalcomponent_kind_to_string(kind)),
7531 txn->req_hdrs);
7532
7533 if (schedule_address) {
7534 mimehdr = charset_encode_mimeheader(schedule_address, 0);
7535 spool_replace_header(xstrdup("X-Schedule-User-Address"),
7536 mimehdr, txn->req_hdrs);
7537 }
7538
7539 time_to_rfc822(icaltime_as_timet_with_zone(icalcomponent_get_dtstamp(comp),
7540 utc_zone),
7541 datestr, sizeof(datestr));
7542 spool_replace_header(xstrdup("Date"), xstrdup(datestr), txn->req_hdrs);
7543
7544 buf_reset(&txn->buf);
7545
7546 /* XXX - validate uid for mime safety? */
7547 if (strchr(uid, '@')) {
7548 buf_printf(&txn->buf, "<%s>", uid);
7549 }
7550 else {
7551 buf_printf(&txn->buf, "<%s@%s>", uid, config_servername);
7552 }
7553 spool_replace_header(xstrdup("Message-ID"),
7554 buf_release(&txn->buf), txn->req_hdrs);
7555
7556 buf_setcstr(&txn->buf, ICALENDAR_CONTENT_TYPE);
7557 if ((meth = icalcomponent_get_method(ical)) != ICAL_METHOD_NONE) {
7558 buf_printf(&txn->buf, "; method=%s",
7559 icalproperty_method_to_string(meth));
7560 }
7561 buf_printf(&txn->buf, "; component=%s", icalcomponent_kind_to_string(kind));
7562 spool_replace_header(xstrdup("Content-Type"),
7563 buf_release(&txn->buf), txn->req_hdrs);
7564
7565 buf_printf(&txn->buf, "attachment;\r\n\tfilename=\"%s\"", resource);
7566 if (sched_tag) buf_printf(&txn->buf, ";\r\n\tschedule-tag=%s", sched_tag);
7567 if (tzbyref) buf_printf(&txn->buf, ";\r\n\ttz-by-ref=true");
7568 spool_replace_header(xstrdup("Content-Disposition"),
7569 buf_release(&txn->buf), txn->req_hdrs);
7570
7571 spool_remove_header(xstrdup("Content-Description"), txn->req_hdrs);
7572
7573 /* Store the resource */
7574 ret = dav_store_resource(txn, icalcomponent_as_ical_string(ical), 0,
7575 mailbox, oldrecord, &imapflags);
7576 strarray_fini(&imapflags);
7577
7578 switch (ret) {
7579 case HTTP_CREATED:
7580 case HTTP_NO_CONTENT:
7581 if (cdata->organizer) {
7582 if (flags & NEW_STAG) txn->resp_body.stag = sched_tag;
7583
7584 if (!(flags & PREFER_REP)) {
7585 /* iCal data has been rewritten - don't return validators */
7586 txn->resp_body.lastmod = 0;
7587 txn->resp_body.etag = NULL;
7588 }
7589 }
7590 break;
7591 }
7592
7593 return ret;
7594 }
7595
7596
7597 static struct mime_type_t freebusy_mime_types[] = {
7598 /* First item MUST be the default type */
7599 { ICALENDAR_CONTENT_TYPE, "2.0", "ifb",
7600 (struct buf* (*)(void *)) &my_icalcomponent_as_ical_string,
7601 NULL, NULL, NULL, NULL
7602 },
7603 { "application/calendar+xml; charset=utf-8", NULL, "xfb",
7604 (struct buf* (*)(void *)) &icalcomponent_as_xcal_string,
7605 NULL, NULL, NULL, NULL
7606 },
7607 { "application/calendar+json; charset=utf-8", NULL, "jfb",
7608 (struct buf* (*)(void *)) &icalcomponent_as_jcal_string,
7609 NULL, NULL, NULL, NULL
7610 },
7611 { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
7612 };
7613
7614
7615 /* Execute a free/busy query per
7616 http://www.calconnect.org/pubdocs/CD0903%20Freebusy%20Read%20URL.pdf */
meth_get_head_fb(struct transaction_t * txn,void * params)7617 static int meth_get_head_fb(struct transaction_t *txn,
7618 void *params __attribute__((unused)))
7619
7620 {
7621 int ret = 0, r, rights;
7622 struct tm *tm;
7623 struct strlist *param;
7624 struct mime_type_t *mime = NULL;
7625 struct propfind_ctx fctx;
7626 struct freebusy_filter fbfilter;
7627 time_t start;
7628 struct icaldurationtype period = icaldurationtype_null_duration();
7629 icalcomponent *cal;
7630
7631 /* Parse the path */
7632 if ((r = caldav_parse_path(txn->req_uri->path,
7633 &txn->req_tgt, &txn->error.desc))) return r;
7634
7635 if (txn->req_tgt.resource ||
7636 !(txn->req_tgt.userid)) {
7637 /* We don't handle GET on a resources or non-calendar collections */
7638 return HTTP_NO_CONTENT;
7639 }
7640
7641 /* Check ACL for current user */
7642 rights = httpd_myrights(httpd_authstate, txn->req_tgt.mbentry);
7643 if (!(rights & DACL_READFB)) {
7644 /* DAV:need-privileges */
7645 txn->error.precond = DAV_NEED_PRIVS;
7646 txn->error.resource = txn->req_tgt.path;
7647 txn->error.rights = DACL_READFB;
7648 return HTTP_NO_PRIVS;
7649 }
7650
7651 if (txn->req_tgt.mbentry->server) {
7652 /* Remote mailbox */
7653 struct backend *be;
7654
7655 be = proxy_findserver(txn->req_tgt.mbentry->server,
7656 &http_protocol, httpd_userid,
7657 &backend_cached, NULL, NULL, httpd_in);
7658 if (!be) return HTTP_UNAVAILABLE;
7659
7660 return http_pipe_req_resp(be, txn);
7661 }
7662
7663 /* Local Mailbox */
7664
7665 /* Check/find 'format' */
7666 param = hash_lookup("format", &txn->req_qparams);
7667 if (param) {
7668 if (param->next /* once only */) return HTTP_BAD_REQUEST;
7669
7670 for (mime = freebusy_mime_types; mime->content_type; mime++) {
7671 if (is_mediatype(param->s, mime->content_type)) break;
7672 }
7673 }
7674 else mime = freebusy_mime_types;
7675
7676 if (!mime || !mime->content_type) return HTTP_NOT_ACCEPTABLE;
7677
7678 memset(&fbfilter, 0, sizeof(struct freebusy_filter));
7679 fbfilter.flags = CHECK_CAL_TRANSP | CHECK_USER_AVAIL;
7680
7681 /* Check for 'start' */
7682 param = hash_lookup("start", &txn->req_qparams);
7683 if (param) {
7684 if (param->next /* once only */) return HTTP_BAD_REQUEST;
7685
7686 fbfilter.start = icaltime_from_rfc3339_string(param->s);
7687 if (icaltime_is_null_time(fbfilter.start)) return HTTP_BAD_REQUEST;
7688
7689 /* Default to end of given day */
7690 start = icaltime_as_timet_with_zone(fbfilter.start, utc_zone);
7691 tm = localtime(&start);
7692
7693 period.seconds = 60 - tm->tm_sec;
7694 period.minutes = 59 - tm->tm_min;
7695 period.hours = 23 - tm->tm_hour;
7696 }
7697 else {
7698 /* Default to start of current day */
7699 start = time(0);
7700 tm = localtime(&start);
7701 tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
7702 fbfilter.start = icaltime_from_timet_with_zone(mktime(tm), 0, utc_zone);
7703
7704 /* Default to 42 day period */
7705 period.days = 42;
7706 }
7707
7708 /* Check for 'period' */
7709 param = hash_lookup("period", &txn->req_qparams);
7710 if (param) {
7711 if (param->next /* once only */ ||
7712 hash_lookup("end", &txn->req_qparams) /* can't use with 'end' */)
7713 return HTTP_BAD_REQUEST;
7714
7715 period = icaldurationtype_from_string(param->s);
7716 if (icaldurationtype_is_bad_duration(period)) return HTTP_BAD_REQUEST;
7717 }
7718
7719 /* Check for 'end' */
7720 param = hash_lookup("end", &txn->req_qparams);
7721 if (param) {
7722 if (param->next /* once only */) return HTTP_BAD_REQUEST;
7723
7724 fbfilter.end = icaltime_from_rfc3339_string(param->s);
7725 if (icaltime_is_null_time(fbfilter.end)) return HTTP_BAD_REQUEST;
7726 }
7727 else {
7728 /* Set end based on period */
7729 fbfilter.end = icaltime_add(fbfilter.start, period);
7730 }
7731
7732
7733 memset(&fctx, 0, sizeof(struct propfind_ctx));
7734 fctx.txn = txn;
7735 fctx.req_tgt = &txn->req_tgt;
7736 fctx.depth = 2;
7737 fctx.userid = httpd_userid;
7738 fctx.userisadmin = httpd_userisadmin;
7739 fctx.authstate = httpd_authstate;
7740 fctx.reqd_privs = 0; /* handled by CALDAV:schedule-deliver on Inbox */
7741 fctx.filter_crit = &fbfilter;
7742 fctx.ret = &ret;
7743
7744 cal = busytime_query_local(txn, &fctx, txn->req_tgt.mbentry->name,
7745 0, NULL, NULL, NULL);
7746
7747 if (fbfilter.freebusy.fb) free(fbfilter.freebusy.fb);
7748
7749 if (cal) {
7750 const char *proto, *host;
7751 icalcomponent *fb;
7752 icalproperty *url;
7753 struct buf *cal_str;
7754
7755 /* Construct URL */
7756 buf_reset(&txn->buf);
7757 http_proto_host(txn->req_hdrs, &proto, &host);
7758 buf_printf(&txn->buf, "%s://%s%s", proto, host, txn->req_uri->path);
7759 if (URI_QUERY(txn->req_uri))
7760 buf_printf(&txn->buf, "?%s", URI_QUERY(txn->req_uri));
7761
7762 /* Set URL property */
7763 fb = icalcomponent_get_first_component(cal, ICAL_VFREEBUSY_COMPONENT);
7764 url = icalproperty_new_url(buf_cstring(&txn->buf));
7765 icalcomponent_add_property(fb, url);
7766
7767 /* Set filename of resource */
7768 buf_reset(&txn->buf);
7769 buf_printf(&txn->buf, "%s.%s",
7770 txn->req_tgt.userid,
7771 mime->file_ext);
7772 txn->resp_body.fname = buf_cstring(&txn->buf);
7773
7774 txn->resp_body.type = mime->content_type;
7775
7776 /* iCalendar data in response should not be transformed */
7777 txn->flags.cc |= CC_NOTRANSFORM;
7778
7779 /* Output the iCalendar object */
7780 cal_str = mime->from_object(cal);
7781 icalcomponent_free(cal);
7782
7783 write_body(HTTP_OK, txn, buf_base(cal_str), buf_len(cal_str));
7784 buf_destroy(cal_str);
7785 }
7786 else ret = HTTP_NOT_FOUND;
7787
7788 return ret;
7789 }
7790
7791
meth_options_cal(struct transaction_t * txn,void * params)7792 static int meth_options_cal(struct transaction_t *txn, void *params)
7793 {
7794 int r;
7795
7796 /* Parse the path */
7797 if ((r = caldav_parse_path(txn->req_uri->path,
7798 &txn->req_tgt, &txn->error.desc))) return r;
7799
7800 if (txn->req_tgt.allow & ALLOW_PATCH) {
7801 /* Add Accept-Patch formats to response */
7802 txn->resp_body.patch = caldav_patch_docs;
7803 }
7804
7805 return meth_options(txn, params);
7806 }
7807