1 /* jmap_api.c -- Routines for handling JMAP API requests
2 *
3 * Copyright (c) 1994-2018 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 #include <config.h>
45
46 #include <sys/time.h>
47 #include <sys/resource.h>
48
49 #include <errno.h>
50
51 #include "append.h"
52 #include "bsearch.h"
53 #include "cyrusdb.h"
54 #include "hash.h"
55 #include "httpd.h"
56 #include "http_dav.h"
57 #include "http_dav_sharing.h"
58 #include "http_jmap.h"
59 #include "imparse.h"
60 #include "mboxname.h"
61 #include "msgrecord.h"
62 #include "proxy.h"
63 #include "times.h"
64 #include "strhash.h"
65 #include "syslog.h"
66 #include "xstrlcpy.h"
67
68 /* generated headers are not necessarily in current directory */
69 #include "imap/http_err.h"
70 #include "imap/imap_err.h"
71 #include "imap/jmap_err.h"
72
73
74 static json_t *extract_value(json_t *from, const char *path, ptrarray_t *refs);
75
extract_array_value(json_t * val,const char * idx,const char * path,ptrarray_t * pool)76 static json_t *extract_array_value(json_t *val, const char *idx,
77 const char *path, ptrarray_t *pool)
78 {
79 if (!strcmp(idx, "*")) {
80 /* Build value from array traversal */
81 json_t *newval = json_pack("[]");
82 size_t i;
83 json_t *v;
84 json_array_foreach(val, i, v) {
85 json_t *x = extract_value(v, path, pool);
86 if (json_is_array(x)) {
87 /* JMAP spec: "If the result of applying the rest
88 * of the pointer tokens to a value was itself an
89 * array, its items should be included individually
90 * in the output rather than including the array
91 * itself." */
92 json_array_extend(newval, x);
93 } else if (x) {
94 json_array_append(newval, x);
95 } else {
96 json_decref(newval);
97 newval = NULL;
98 }
99 }
100 if (newval) {
101 ptrarray_add(pool, newval);
102 }
103 return newval;
104 }
105
106 /* Lookup array value by index */
107 const char *eot = NULL;
108 bit64 num;
109 if (parsenum(idx, &eot, 0, &num) || *eot) {
110 return NULL;
111 }
112 val = json_array_get(val, num);
113 if (!val) {
114 return NULL;
115 }
116 return extract_value(val, path, pool);
117 }
118
119 /* Extract the JSON value at position path from val.
120 *
121 * Return NULL, if the value does not exist or if
122 * path is erroneous.
123 */
extract_value(json_t * val,const char * path,ptrarray_t * pool)124 static json_t *extract_value(json_t *val, const char *path, ptrarray_t *pool)
125 {
126 /* Return value for empty path */
127 if (*path == '\0') {
128 return val;
129 }
130
131 /* Be lenient: root path '/' is optional */
132 if (*path == '/') {
133 path++;
134 }
135
136 /* Walk over path segments */
137 while (val && *path) {
138 const char *top = NULL;
139 char *p = NULL;
140
141 /* Extract next path segment */
142 if (!(top = strchr(path, '/'))) {
143 top = strchr(path, '\0');
144 }
145 p = jmap_pointer_decode(path, top - path);
146 if (*p == '\0') {
147 return NULL;
148 }
149
150 /* Extract array value */
151 if (json_is_array(val)) {
152 val = extract_array_value(val, p, top, pool);
153 free(p);
154 return val;
155 }
156
157 /* Value MUST be an object now */
158 if (!json_is_object(val)) {
159 free(p);
160 return NULL;
161 }
162 /* Step down into object tree */
163 val = json_object_get(val, p);
164 free(p);
165 path = *top ? top + 1 : top;
166 }
167
168 return val;
169 }
170
process_resultrefs(json_t * args,json_t * resp,json_t ** err)171 static int process_resultrefs(json_t *args, json_t *resp, json_t **err)
172 {
173 json_t *ref;
174 const char *arg;
175 int ret = -1;
176
177 void *tmp;
178 json_object_foreach_safe(args, tmp, arg, ref) {
179 if (*arg != '#' || *(arg+1) == '\0') {
180 continue;
181 }
182
183 if (json_object_get(args, arg + 1)) {
184 *err = json_pack("{s:s, s:[s]}",
185 "type", "invalidArguments", "arguments", arg);
186 goto fail;
187 }
188
189 const char *of, *path, *name;
190 json_t *res = NULL;
191
192 /* Parse result reference object */
193 of = json_string_value(json_object_get(ref, "resultOf"));
194 if (!of || *of == '\0') {
195 goto fail;
196 }
197 path = json_string_value(json_object_get(ref, "path"));
198 if (!path || *path == '\0') {
199 goto fail;
200 }
201 name = json_string_value(json_object_get(ref, "name"));
202 if (!name || *name == '\0') {
203 goto fail;
204 }
205
206 /* Lookup referenced response */
207 json_t *v;
208 size_t i;
209 json_array_foreach(resp, i, v) {
210 const char *tag = json_string_value(json_array_get(v, 2));
211 if (!tag || strcmp(tag, of)) {
212 continue;
213 }
214 const char *mname = json_string_value(json_array_get(v, 0));
215 if (!mname || strcmp(name, mname)) {
216 goto fail;
217 }
218 res = v;
219 break;
220 }
221 if (!res) goto fail;
222
223 /* Extract the reference argument value. */
224 /* We maintain our own pool of newly created JSON objects, since
225 * tracking reference counts across newly created JSON arrays is
226 * a pain. Rule: If you incref an existing JSON value or create
227 * an entirely new one, put it into the pool for cleanup. */
228 ptrarray_t pool = PTRARRAY_INITIALIZER;
229 json_t *val = extract_value(json_array_get(res, 1), path, &pool);
230 if (!val) goto fail;
231
232 /* Replace both key and value of the reference entry */
233 json_object_set(args, arg + 1, val);
234 json_object_del(args, arg);
235
236 /* Clean up reference counts of pooled JSON objects */
237 json_t *ref;
238 while ((ref = ptrarray_pop(&pool))) {
239 json_decref(ref);
240 }
241 ptrarray_fini(&pool);
242 }
243
244 return 0;
245
246 fail:
247 return ret;
248 }
249
parse_json_body(struct transaction_t * txn,json_t ** req)250 static int parse_json_body(struct transaction_t *txn, json_t **req)
251 {
252 const char **hdr;
253 json_error_t jerr;
254 int ret;
255
256 /* Check Content-Type */
257 if (!(hdr = spool_getheader(txn->req_hdrs, "Content-Type")) ||
258 !is_mediatype("application/json", hdr[0])) {
259 txn->error.desc = "This method requires a JSON request body";
260 return HTTP_BAD_MEDIATYPE;
261 }
262
263 /* Read body */
264 txn->req_body.flags |= BODY_DECODE;
265 ret = http_read_req_body(txn);
266 if (ret) {
267 txn->flags.conn = CONN_CLOSE;
268 return ret;
269 }
270
271 /* Parse the JSON request */
272 *req = json_loadb(buf_base(&txn->req_body.payload),
273 buf_len(&txn->req_body.payload),
274 0, &jerr);
275 if (!*req) {
276 buf_reset(&txn->buf);
277 buf_printf(&txn->buf,
278 "Unable to parse JSON request body: %s", jerr.text);
279 txn->error.desc = buf_cstring(&txn->buf);
280 return JMAP_NOT_JSON;
281 }
282
283 return 0;
284 }
285
validate_request(struct transaction_t * txn,json_t * req,jmap_settings_t * settings)286 static int validate_request(struct transaction_t *txn, json_t *req,
287 jmap_settings_t *settings)
288 {
289 json_t *using = json_object_get(req, "using");
290 json_t *calls = json_object_get(req, "methodCalls");
291
292 if (!json_is_array(using) || !json_is_array(calls)) {
293 return JMAP_NOT_REQUEST;
294 }
295
296 /*
297 * XXX the following maximums are not enforced:
298 * maxConcurrentUpload
299 * maxConcurrentRequests
300 */
301
302 if (buf_len(&txn->req_body.payload) >
303 (size_t) settings->limits[MAX_SIZE_REQUEST]) {
304 return JMAP_LIMIT_SIZE;
305 }
306
307 size_t i;
308 json_t *val;
309 json_array_foreach(calls, i, val) {
310 if (json_array_size(val) != 3 ||
311 !json_is_string(json_array_get(val, 0)) ||
312 !json_is_object(json_array_get(val, 1)) ||
313 !json_is_string(json_array_get(val, 2))) {
314 return JMAP_NOT_REQUEST;
315 }
316 if (i >= (size_t) settings->limits[MAX_CALLS_IN_REQUEST]) {
317 return JMAP_LIMIT_CALLS;
318 }
319 const char *mname = json_string_value(json_array_get(val, 0));
320 mname = strchr(mname, '/');
321 if (!mname) continue;
322
323 mname++;
324 if (!strcmp(mname, "get")) {
325 json_t *ids = json_object_get(json_array_get(val, 1), "ids");
326 if (json_array_size(ids) >
327 (size_t) settings->limits[MAX_OBJECTS_IN_GET]) {
328 return JMAP_LIMIT_OBJS_GET;
329 }
330 }
331 else if (!strcmp(mname, "set")) {
332 json_t *args = json_array_get(val, 1);
333 size_t size = json_object_size(json_object_get(args, "create"));
334 size += json_object_size(json_object_get(args, "update"));
335 size += json_array_size(json_object_get(args, "destroy"));
336 if (size > (size_t) settings->limits[MAX_OBJECTS_IN_SET]) {
337 return JMAP_LIMIT_OBJS_SET;
338 }
339 }
340 }
341
342 json_array_foreach(using, i, val) {
343 const char *s = json_string_value(val);
344 if (!s) {
345 return JMAP_NOT_REQUEST;
346 }
347 else if (!strcmp(s, "ietf:jmap")) {
348 syslog(LOG_DEBUG, "old capability %s used", s);
349 }
350 else if (!strcmp(s, "ietf:jmapmail")) {
351 syslog(LOG_DEBUG, "old capability %s used", s);
352 }
353 else if (!json_object_get(settings->server_capabilities, s)) {
354 buf_printf(&txn->buf, "The Request object used capability '%s',"
355 " which is not supported by this server.", s);
356 txn->error.desc = buf_cstring(&txn->buf);
357 return JMAP_UNKNOWN_CAPABILITY;
358 }
359 }
360
361 return 0;
362 }
363
jmap_is_valid_id(const char * id)364 HIDDEN int jmap_is_valid_id(const char *id)
365 {
366 if (!id || *id == '\0') return 0;
367 const char *p;
368 for (p = id; *p; p++) {
369 if (('0' <= *p && *p <= '9'))
370 continue;
371 if (('a' <= *p && *p <= 'z') || ('A' <= *p && *p <= 'Z'))
372 continue;
373 if ((*p == '-') || (*p == '_'))
374 continue;
375 return 0;
376 }
377 return 1;
378 }
379
_make_created_ids(const char * creation_id,void * val,void * rock)380 static void _make_created_ids(const char *creation_id, void *val, void *rock)
381 {
382 json_t *jcreatedIds = rock;
383 const char *id = val;
384 json_object_set_new(jcreatedIds, creation_id, json_string(id));
385 }
386
jmap_error_response(struct transaction_t * txn,long code,json_t ** res)387 static int jmap_error_response(struct transaction_t *txn,
388 long code, json_t **res)
389 {
390 long http_code = HTTP_BAD_REQUEST;
391 const char *type, *title, *limit = NULL;
392
393 /* Error string is encoded as type NUL title [ NUL limit ] */
394 type = error_message(code);
395 title = type + strlen(type) + 1;
396
397 switch (code) {
398 case JMAP_NOT_JSON:
399 case JMAP_NOT_REQUEST:
400 case JMAP_UNKNOWN_CAPABILITY:
401 break;
402
403 case JMAP_LIMIT_SIZE:
404 http_code = HTTP_PAYLOAD_TOO_LARGE;
405
406 GCC_FALLTHROUGH
407
408 case JMAP_LIMIT_CALLS:
409 case JMAP_LIMIT_OBJS_GET:
410 case JMAP_LIMIT_OBJS_SET:
411 limit = title + strlen(title) + 1;
412 break;
413
414 default:
415 /* Actually an HTTP code, not a JMAP error code */
416 return code;
417 }
418
419 if (txn->meth == METH_UNKNOWN) {
420 /* API request over WebSocket */
421 *res = json_pack("{s:s s:s s:s s:i}",
422 "@type", "RequestError", "type", type, "title", title,
423 "status", atoi(error_message(http_code)));
424 }
425 else {
426 *res = json_pack("{s:s s:s s:i}", "type", type, "title", title,
427 "status", atoi(error_message(http_code)));
428 }
429 if (!*res) {
430 txn->error.desc = "Unable to create JSON response";
431 return HTTP_SERVER_ERROR;
432 }
433
434 if (limit) {
435 json_object_set_new(*res, "limit", json_string(limit));
436 }
437
438 if (txn->error.desc) {
439 json_object_set_new(*res, "detail", json_string(txn->error.desc));
440 }
441
442 return http_code;
443 }
444
445
jmap_initreq(jmap_req_t * req)446 HIDDEN int jmap_initreq(jmap_req_t *req)
447 {
448 memset(req, 0, sizeof(struct jmap_req));
449 req->mboxes = ptrarray_new();
450 return 0;
451 }
452
453 struct _mboxcache_rec {
454 struct mailbox *mbox;
455 int refcount;
456 int rw;
457 };
458
jmap_finireq(jmap_req_t * req)459 HIDDEN void jmap_finireq(jmap_req_t *req)
460 {
461 int i;
462
463 for (i = 0; i < req->mboxes->count; i++) {
464 struct _mboxcache_rec *rec = ptrarray_nth(req->mboxes, i);
465 syslog(LOG_ERR, "jmap: force-closing mailbox %s (refcount=%d)",
466 rec->mbox->name, rec->refcount);
467 mailbox_close(&rec->mbox);
468 free(rec);
469 }
470 /* Fail after cleaning up open mailboxes */
471 if (req->mboxes->count) {
472 json_t *jdebug = json_pack("[s,s,s,o,o]", req->method, req->userid, req->accountid, req->args, req->response);
473 char *debug = json_dumps(jdebug, JSON_INDENT(2));
474 assert(!debug);
475 }
476
477 ptrarray_free(req->mboxes);
478 req->mboxes = NULL;
479
480 jmap_mbentry_cache_free(req);
481
482 json_decref(req->perf_details);
483 req->perf_details = NULL;
484 }
485
find_methodproc(const char * name,hash_table * jmap_methods)486 static jmap_method_t *find_methodproc(const char *name, hash_table *jmap_methods)
487 {
488 return hash_lookup(name, jmap_methods);
489 }
490
491 struct mbstate {
492 int mbtype;
493 int rights; // ACL for current user
494 };
495
_mbstate_getoradd(struct auth_state * authstate,const mbentry_t * mbentry,hash_table * mbstates)496 static struct mbstate *_mbstate_getoradd(struct auth_state *authstate,
497 const mbentry_t *mbentry,
498 hash_table *mbstates)
499 {
500 struct mbstate *mbstate = hash_lookup(mbentry->name, mbstates);
501 if (mbstate) return mbstate;
502
503 mbstate = xmalloc(sizeof(struct mbstate));
504 mbstate->mbtype = mbentry->mbtype;
505
506 /* Lookup ACL */
507 mbname_t *mbname = mbname_from_intname(mbentry->name);
508 if (mbentry->mbtype & MBTYPE_INTERMEDIATE) {
509 // if it's an intermediate mailbox, we get rights from the parent
510 mbentry_t *parententry = NULL;
511 if (mboxlist_findparent(mbentry->name, &parententry))
512 mbstate->rights = 0;
513 else
514 mbstate->rights = httpd_myrights(authstate, parententry);
515 mboxlist_entry_free(&parententry);
516 }
517 else mbstate->rights = httpd_myrights(authstate, mbentry);
518 mbname_free(&mbname);
519
520 hash_insert(mbentry->name, mbstate, mbstates);
521 return mbstate;
522 }
523
524
525 /* Return the ACL for mbentry for the authstate of userid.
526 * Lookup and store ACL rights in the cached mailbox state. */
_rights_for_mbentry(struct auth_state * authstate,const mbentry_t * mbentry,hash_table * mbstates)527 static int _rights_for_mbentry(struct auth_state *authstate,
528 const mbentry_t *mbentry,
529 hash_table *mbstates)
530 {
531 if (!mbentry) return 0;
532 struct mbstate *mbstate =_mbstate_getoradd(authstate, mbentry, mbstates);
533 return mbstate->rights;
534 }
535
536 struct capabilities_rock {
537 const char *authuserid;
538 hash_table *mboxrights;
539 struct auth_state *authstate;
540
541 int is_visible;
542 int has_mail;
543 int has_contacts;
544 int has_calendars;
545 };
546
capabilities_cb(const mbentry_t * mbentry,void * vrock)547 static int capabilities_cb(const mbentry_t *mbentry, void *vrock)
548 {
549 struct capabilities_rock *rock = vrock;
550
551 if (!mbentry) return 0;
552
553 if ((mbentry->mbtype & MBTYPE_DELETED) ||
554 (mbentry->mbtype & MBTYPE_MOVING) ||
555 (mbentry->mbtype & MBTYPE_REMOTE) ||
556 (mbentry->mbtype & MBTYPE_RESERVE)) {
557 return 0;
558 }
559
560 int rights = _rights_for_mbentry(rock->authstate, mbentry, rock->mboxrights);
561 if (!(rights & JACL_LOOKUP)) return 0;
562 rock->is_visible = 1;
563
564 mbname_t *mbname = mbname_from_intname(mbentry->name);
565 const strarray_t *boxes = mbname_boxes(mbname);
566 if (!rock->has_mail) {
567 rock->has_mail = mbentry->mbtype == MBTYPE_EMAIL;
568 }
569 if (!rock->has_contacts) {
570 rock->has_contacts = strarray_size(boxes) >= 1 &&
571 !strcmpsafe(config_getstring(IMAPOPT_ADDRESSBOOKPREFIX),
572 strarray_nth(boxes, 0));
573 }
574 if (!rock->has_calendars) {
575 rock->has_calendars = strarray_size(boxes) >= 1 &&
576 !strcmpsafe(config_getstring(IMAPOPT_CALENDARPREFIX),
577 strarray_nth(boxes, 0));
578 }
579 mbname_free(&mbname);
580
581 return 0;
582 }
583
lookup_capabilities(const char * accountid,const char * authuserid,struct auth_state * authstate,hash_table * mboxrights)584 static json_t *lookup_capabilities(const char *accountid,
585 const char *authuserid,
586 struct auth_state *authstate,
587 hash_table *mboxrights)
588 {
589 // we need to know if we can write children of the inbox
590 mbentry_t *inboxentry = NULL;
591
592 char *inboxname = mboxname_user_mbox(accountid, NULL);
593 if (mboxlist_lookup(inboxname, &inboxentry, NULL)) {
594 free(inboxname);
595 return json_null();
596 }
597 free(inboxname);
598
599 int inboxrights = _rights_for_mbentry(authstate, inboxentry, mboxrights);
600 mboxlist_entry_free(&inboxentry);
601
602 json_t *capas = json_object();
603
604 int mayCreateTopLevel = (inboxrights & JACL_CREATECHILD) ? 1 : 0;
605
606 if (!strcmp(authuserid, accountid)) {
607 /* Primary account has all capabilities */
608 jmap_core_capabilities(capas);
609 jmap_mail_capabilities(capas, mayCreateTopLevel);
610 jmap_emailsubmission_capabilities(capas);
611 jmap_mdn_capabilities(capas);
612 jmap_vacation_capabilities(capas);
613 jmap_contact_capabilities(capas);
614 jmap_calendar_capabilities(capas);
615 jmap_backup_capabilities(capas);
616 jmap_notes_capabilities(capas);
617 #ifdef USE_SIEVE
618 jmap_sieve_capabilities(capas);
619 #endif
620 }
621 else {
622 /* Lookup capabilities for shared account */
623 struct capabilities_rock rock = {
624 authuserid, mboxrights, httpd_authstate, 0, 0, 0, 0
625 };
626 mboxlist_usermboxtree(accountid, authstate, capabilities_cb,
627 &rock, MBOXTREE_INTERMEDIATES);
628 if (rock.is_visible) {
629 jmap_core_capabilities(capas);
630 if (rock.has_mail) {
631 // we don't offer emailsubmission or vacation
632 // for shared accounts right now
633 jmap_mail_capabilities(capas, mayCreateTopLevel);
634 }
635 if (rock.has_contacts) {
636 jmap_contact_capabilities(capas);
637 }
638 if (rock.has_calendars) {
639 jmap_calendar_capabilities(capas);
640 }
641 // should we offer Backup/restoreXxx for shared accounts?
642 }
643 }
644
645 if (!json_object_size(capas)) {
646 json_decref(capas);
647 capas = json_null();
648 }
649 return capas;
650 }
651
_free_json(void * val)652 static void _free_json(void *val)
653 {
654 json_decref((json_t *)val);
655 }
656
_free_buf(void * val)657 static void _free_buf(void *val)
658 {
659 buf_destroy((struct buf *)val);
660 }
661
662 /* Perform an API request */
jmap_api(struct transaction_t * txn,json_t ** res,jmap_settings_t * settings)663 HIDDEN int jmap_api(struct transaction_t *txn, json_t **res,
664 jmap_settings_t *settings)
665 {
666 json_t *jreq = NULL, *resp = NULL;
667 size_t i;
668 int ret, do_perf = 0;
669 char *account_inboxname = NULL;
670 int return_created_ids = 0;
671 hash_table created_ids = HASH_TABLE_INITIALIZER;
672 hash_table inmemory_blobs = HASH_TABLE_INITIALIZER;
673 hash_table capabilities_by_accountid = HASH_TABLE_INITIALIZER;
674 hash_table mbstates = HASH_TABLE_INITIALIZER;
675 strarray_t methods = STRARRAY_INITIALIZER;
676 ptrarray_t method_calls = PTRARRAY_INITIALIZER;
677 ptrarray_t processed_methods = PTRARRAY_INITIALIZER;
678 strarray_t using_capabilities = STRARRAY_INITIALIZER;
679
680 ret = parse_json_body(txn, &jreq);
681 if (ret) return jmap_error_response(txn, ret, res);
682
683 /* Validate Request object */
684 if ((ret = validate_request(txn, jreq, settings))) {
685 json_decref(jreq);
686 return jmap_error_response(txn, ret, res);
687 }
688
689 /* Start JSON response */
690 resp = json_array();
691 if (!resp) {
692 txn->error.desc = "Unable to create JSON response body";
693 ret = HTTP_SERVER_ERROR;
694 goto done;
695 }
696
697 /* Set up request-internal state */
698 construct_hash_table(&capabilities_by_accountid, 8, 0);
699 construct_hash_table(&inmemory_blobs, 64, 0);
700 construct_hash_table(&mbstates, 64, 0);
701 construct_hash_table(&created_ids, 1024, 0);
702
703 /* Parse client-supplied creation ids */
704 json_t *jcreatedIds = json_object_get(jreq, "createdIds");
705 if (json_is_object(jcreatedIds)) {
706 return_created_ids = 1;
707 const char *creation_id;
708 json_t *jval;
709 json_object_foreach(jcreatedIds, creation_id, jval) {
710 if (!json_is_string(jval)) {
711 txn->error.desc = "Invalid createdIds argument";
712 ret = HTTP_BAD_REQUEST;
713 goto done;
714 }
715 const char *id = json_string_value(jval);
716 if (!jmap_is_valid_id(creation_id) || !jmap_is_valid_id(id)) {
717 txn->error.desc = "Invalid createdIds argument";
718 ret = HTTP_BAD_REQUEST;
719 goto done;
720 }
721 hash_insert(creation_id, xstrdup(id), &created_ids);
722 }
723 }
724 else if (jcreatedIds && jcreatedIds != json_null()) {
725 txn->error.desc = "Invalid createdIds argument";
726 ret = HTTP_BAD_REQUEST;
727 goto done;
728 }
729
730 json_t *jusing = json_object_get(jreq, "using");
731 for (i = 0; i < json_array_size(jusing); i++) {
732 strarray_add(&using_capabilities, json_string_value(json_array_get(jusing, i)));
733 }
734
735 /* Push client method calls on call stack */
736 json_t *jmethod_calls = json_object_get(jreq, "methodCalls");
737 for (i = json_array_size(jmethod_calls); i > 0; i--) {
738 json_t *mc = json_array_get(jmethod_calls, i-1);
739 ptrarray_push(&method_calls, json_incref(mc));
740 }
741
742 /* Process call stack */
743 do_perf = strarray_find(&using_capabilities, JMAP_PERFORMANCE_EXTENSION, 0) >= 0;
744 json_t *mc;
745 while ((mc = ptrarray_pop(&method_calls))) {
746 /* Send provisional response, if necessary */
747 keepalive_response(txn);
748
749 /* Mark method as processed */
750 ptrarray_push(&processed_methods, mc);
751
752 /* Process method */
753 const jmap_method_t *mp;
754 const char *mname = json_string_value(json_array_get(mc, 0));
755 json_t *args = json_array_get(mc, 1);
756 const char *tag = json_string_value(json_array_get(mc, 2));
757 int r = 0;
758
759 strarray_append(&methods, mname);
760 json_incref(args);
761
762 /* Find the message processor */
763 mp = find_methodproc(mname, &settings->methods);
764 if (!mp || strarray_find(&using_capabilities, mp->capability, 0) < 0) {
765 json_array_append_new(resp, json_pack("[s {s:s} s]",
766 "error", "type", "unknownMethod", tag));
767 json_decref(args);
768 continue;
769 }
770
771 /* Validate accountId argument */
772 const char *accountid = httpd_userid;
773 json_t *err = NULL;
774 json_t *arg = json_object_get(args, "accountId");
775 if (arg && arg != json_null()) {
776 accountid = json_string_value(arg);
777 }
778 if (!accountid) {
779 err = json_pack("{s:s, s:[s]}",
780 "type", "invalidArguments", "arguments", "accountId");
781 json_array_append_new(resp, json_pack("[s,o,s]", "error", err, tag));
782 json_decref(args);
783 continue;
784 }
785
786 /* Validate supported capabilities for this account */
787 json_t *account_capas = hash_lookup(accountid, &capabilities_by_accountid);
788 if (!account_capas) {
789 account_capas = lookup_capabilities(accountid, httpd_userid,
790 httpd_authstate, &mbstates);
791 hash_insert(accountid, account_capas, &capabilities_by_accountid);
792 }
793 if (json_is_null(account_capas)) {
794 err = json_pack("{s:s}", "type", "accountNotFound");
795 }
796 else if (!json_object_get(account_capas, mp->capability)) {
797 err = json_pack("{s:s}", "type", "accountNotSupportedByMethod");
798 }
799 if (err) {
800 json_array_append_new(resp, json_pack("[s,o,s]", "error", err, tag));
801 json_decref(args);
802 continue;
803 }
804
805 /* Pre-process result references */
806 if (process_resultrefs(args, resp, &err)) {
807 if (!err) err = json_pack("{s:s}", "type", "resultReference");
808
809 json_array_append_new(resp, json_pack("[s,o,s]", "error", err, tag));
810 json_decref(args);
811 continue;
812 }
813
814 if (config_getswitch(IMAPOPT_READONLY) && (mp->flags & JMAP_READ_WRITE)) {
815 if (!err) err = json_pack("{s:s}", "type", "accountReadOnly");
816
817 json_array_append_new(resp, json_pack("[s,o,s]", "error", err, tag));
818 json_decref(args);
819 continue;
820 }
821
822 struct conversations_state *cstate = NULL;
823 if (mp->flags & JMAP_NEED_CSTATE) {
824 r = conversations_open_user(accountid,
825 !(mp->flags & JMAP_READ_WRITE), &cstate);
826
827 if (r) {
828 txn->error.desc = error_message(r);
829 ret = HTTP_SERVER_ERROR;
830 json_decref(args);
831 goto done;
832 }
833 }
834
835 /* Initialize request context */
836 struct jmap_req req;
837 jmap_initreq(&req);
838
839 req.method = mname;
840 req.userid = httpd_userid;
841 req.accountid = accountid;
842 req.cstate = cstate;
843 req.authstate = httpd_authstate;
844 req.args = args;
845 req.response = resp;
846 req.tag = tag;
847 req.created_ids = &created_ids;
848 req.txn = txn;
849 req.mbstates = &mbstates;
850 req.method_calls = &method_calls;
851 req.using_capabilities = &using_capabilities;
852 req.inmemory_blobs = &inmemory_blobs;
853
854 if (do_perf) {
855 struct rusage usage;
856
857 getrusage(RUSAGE_SELF, &usage);
858 req.user_start = timeval_get_double(&usage.ru_utime);
859 req.sys_start = timeval_get_double(&usage.ru_stime);
860 req.real_start = now_ms() / 1000.0;
861 req.perf_details = json_object();
862 }
863
864 /* Read the current state data in */
865 account_inboxname = mboxname_user_mbox(accountid, NULL);
866 r = mboxname_read_counters(account_inboxname, &req.counters);
867 free(account_inboxname);
868 account_inboxname = NULL;
869 if (r) {
870 conversations_abort(&req.cstate);
871 txn->error.desc = error_message(r);
872 ret = HTTP_SERVER_ERROR;
873 jmap_finireq(&req);
874 json_decref(args);
875 goto done;
876 }
877
878 /* Call the message processor. */
879 r = mp->proc(&req);
880
881 /* Finalize request context */
882 jmap_finireq(&req);
883
884 if (r) {
885 conversations_abort(&req.cstate);
886 txn->error.desc = error_message(r);
887 ret = HTTP_SERVER_ERROR;
888 json_decref(args);
889 goto done;
890 }
891 conversations_commit(&req.cstate);
892
893 json_decref(args);
894 }
895
896 /* Build response */
897 if (txn->meth == METH_UNKNOWN) {
898 /* API request over WebSocket */
899 *res = json_pack("{s:s s:O}",
900 "@type", "Response", "methodResponses", resp);
901 }
902 else {
903 *res = json_pack("{s:O}", "methodResponses", resp);
904 }
905 if (return_created_ids) {
906 json_t *jcreatedIds = json_object();
907 hash_enumerate(&created_ids, _make_created_ids, jcreatedIds);
908 json_object_set_new(*res, "createdIds", jcreatedIds);
909 }
910 char *user_inboxname = mboxname_user_mbox(httpd_userid, NULL);
911 struct buf state = BUF_INITIALIZER;
912 buf_printf(&state, MODSEQ_FMT, mboxname_readraclmodseq(user_inboxname));
913 free(user_inboxname);
914 json_object_set_new(*res, "sessionState", json_string(buf_cstring(&state)));
915 buf_free(&state);
916
917 done:
918 /* tell syslog which methods were called */
919 spool_replace_header(xstrdup(":jmap"),
920 strarray_join(&methods, ","), txn->req_hdrs);
921
922 if ((txn->meth == METH_UNKNOWN) && strarray_size(httpd_log_headers)) {
923 /* API request over WebSocket - add logheaders */
924 json_t *jlogHeaders = json_object_get(jreq, "logHeaders");
925 struct buf logbuf = BUF_INITIALIZER;
926 const char *hdrname;
927 json_t *jval;
928
929 json_object_foreach(jlogHeaders, hdrname, jval) {
930 const char *val = json_string_value(jval);
931
932 if (val &&
933 strarray_find_case(httpd_log_headers, hdrname, 0) >= 0) {
934 buf_printf(&logbuf, "; %s=\"%s\"", hdrname, val);
935 }
936 }
937
938 spool_replace_header(xstrdup(":logheaders"),
939 buf_release(&logbuf), txn->req_hdrs);
940 }
941
942 {
943 /* Clean up call stack */
944 json_t *jval;
945 while ((jval = ptrarray_pop(&processed_methods))) {
946 json_decref(jval);
947 }
948 while ((jval = ptrarray_pop(&method_calls))) {
949 json_decref(jval);
950 }
951 ptrarray_fini(&processed_methods);
952 ptrarray_fini(&method_calls);
953 }
954 free_hash_table(&created_ids, free);
955 free_hash_table(&inmemory_blobs, _free_buf);
956 free_hash_table(&capabilities_by_accountid, _free_json);
957 free_hash_table(&mbstates, free);
958 free(account_inboxname);
959 json_decref(jreq);
960 json_decref(resp);
961 strarray_fini(&methods);
962 strarray_fini(&using_capabilities);
963
964 return ret;
965 }
966
967 struct findaccounts_rock {
968 struct buf current_accountid;
969 int current_rights;
970 json_t *accounts;
971 const char *authuserid;
972 };
973
findaccounts_add(struct findaccounts_rock * rock)974 static void findaccounts_add(struct findaccounts_rock *rock)
975 {
976 if (!buf_len(&rock->current_accountid))
977 return;
978
979 if (!(rock->current_rights & JACL_READITEMS))
980 return;
981
982 const char *accountid = buf_cstring(&rock->current_accountid);
983 int is_rw = rock->current_rights & JACL_WRITE;
984 int is_primary = !strcmp(rock->authuserid, accountid);
985
986 if (config_getswitch(IMAPOPT_READONLY)) is_rw = 0;
987
988 json_t *account = json_object();
989 json_object_set_new(account, "name", json_string(accountid));
990 json_object_set_new(account, "isPrimary", json_boolean(is_primary));
991 json_object_set_new(account, "isPersonal", json_boolean(is_primary));
992 json_object_set_new(account, "isReadOnly", json_boolean(!is_rw));
993 json_object_set_new(rock->accounts, accountid, account);
994 }
995
findaccounts_cb(struct findall_data * data,void * vrock)996 static int findaccounts_cb(struct findall_data *data, void *vrock)
997 {
998 if (!data || !data->mbentry) {
999 return 0;
1000 }
1001
1002 struct findaccounts_rock *rock = vrock;
1003 const mbentry_t *mbentry = data->mbentry;
1004 mbname_t *mbname = mbname_from_intname(mbentry->name);
1005
1006 if (strcmp(buf_cstring(&rock->current_accountid), mbname_userid(mbname))) {
1007 findaccounts_add(rock);
1008 buf_setcstr(&rock->current_accountid, mbname_userid(mbname));
1009 rock->current_rights = 0;
1010 }
1011 rock->current_rights |= httpd_myrights(httpd_authstate, data->mbentry);
1012
1013 mbname_free(&mbname);
1014 return 0;
1015 }
1016
jmap_accounts(json_t * accounts,json_t * primary_accounts)1017 HIDDEN void jmap_accounts(json_t *accounts, json_t *primary_accounts)
1018 {
1019 /* Find shared accounts */
1020 strarray_t patterns = STRARRAY_INITIALIZER;
1021 char *userpat = xstrdup("user.*");
1022 userpat[4] = jmap_namespace.hier_sep;
1023 strarray_append(&patterns, userpat);
1024 struct findaccounts_rock rock = {
1025 BUF_INITIALIZER, 0, accounts, httpd_userid
1026 };
1027 int r = mboxlist_findallmulti(&jmap_namespace, &patterns, 0, httpd_userid,
1028 httpd_authstate, findaccounts_cb, &rock);
1029 if (r) {
1030 syslog(LOG_ERR, "Can't determine shared JMAP accounts for user %s: %s",
1031 httpd_userid, error_message(r));
1032 }
1033 findaccounts_add(&rock);
1034
1035 /* Add primary accout */
1036 buf_setcstr(&rock.current_accountid, httpd_userid);
1037 rock.current_rights = JACL_ALL;
1038 findaccounts_add(&rock);
1039
1040 /* Determine account capabilities */
1041 hash_table mboxrights = HASH_TABLE_INITIALIZER;
1042 construct_hash_table(&mboxrights, 64, 0);
1043 json_t *jaccount;
1044 const char *accountid;
1045 json_object_foreach(accounts, accountid, jaccount) {
1046 json_t *capas = lookup_capabilities(accountid, httpd_userid,
1047 httpd_authstate, &mboxrights);
1048 json_object_set_new(jaccount, "accountCapabilities", capas);
1049 }
1050 free_hash_table(&mboxrights, free);
1051
1052 json_t *jprimary = json_string(httpd_userid);
1053 json_object_set(primary_accounts, JMAP_URN_MAIL, jprimary);
1054 json_object_set(primary_accounts, JMAP_URN_SUBMISSION, jprimary);
1055 json_object_set(primary_accounts, JMAP_CONTACTS_EXTENSION, jprimary);
1056 json_object_set(primary_accounts, JMAP_CALENDARS_EXTENSION, jprimary);
1057 json_object_set(primary_accounts, JMAP_BACKUP_EXTENSION, jprimary);
1058 #ifdef USE_SIEVE
1059 json_object_set(primary_accounts, JMAP_URN_VACATION, jprimary);
1060 json_object_set(primary_accounts, JMAP_SIEVE_EXTENSION, jprimary);
1061 #endif
1062 json_decref(jprimary);
1063
1064 /* Clean up */
1065 buf_free(&rock.current_accountid);
1066 free(userpat);
1067 strarray_fini(&patterns);
1068 }
1069
1070
jmap_add_subreq(jmap_req_t * req,const char * method,json_t * args,const char * client_id)1071 HIDDEN void jmap_add_subreq(jmap_req_t *req, const char *method,
1072 json_t *args, const char *client_id)
1073 {
1074 if (!client_id) client_id = req->tag;
1075 ptrarray_push(req->method_calls, json_pack("[s,o,s]", method, args, client_id));
1076 }
1077
jmap_lookup_id(jmap_req_t * req,const char * creation_id)1078 const char *jmap_lookup_id(jmap_req_t *req, const char *creation_id)
1079 {
1080 return hash_lookup(creation_id, req->created_ids);
1081 }
1082
jmap_id_string_value(jmap_req_t * req,json_t * item)1083 const char *jmap_id_string_value(jmap_req_t *req, json_t *item)
1084 {
1085 if (!item) return NULL;
1086 if (!json_is_string(item)) return NULL;
1087 const char *id = json_string_value(item);
1088 if (*id == '#')
1089 return jmap_lookup_id(req, id+1);
1090 return id;
1091 }
1092
jmap_add_id(jmap_req_t * req,const char * creation_id,const char * id)1093 void jmap_add_id(jmap_req_t *req, const char *creation_id, const char *id)
1094 {
1095 /* It's OK to overwrite existing ids, as per Foo/set:
1096 * "A client SHOULD NOT reuse a creation id anywhere in the same API
1097 * request. If a creation id is reused, the server MUST map the creation
1098 * id to the most recently created item with that id."
1099 */
1100 free(hash_del(creation_id, req->created_ids));
1101 hash_insert(creation_id, xstrdup(id), req->created_ids);
1102 }
1103
jmap_openmbox(jmap_req_t * req,const char * name,struct mailbox ** mboxp,int rw)1104 HIDDEN int jmap_openmbox(jmap_req_t *req, const char *name,
1105 struct mailbox **mboxp, int rw)
1106 {
1107 int i, r;
1108 struct _mboxcache_rec *rec;
1109
1110 for (i = 0; i < req->mboxes->count; i++) {
1111 rec = (struct _mboxcache_rec*) ptrarray_nth(req->mboxes, i);
1112 if (!strcmp(name, rec->mbox->name)) {
1113 if (rw && !rec->rw) {
1114 /* Lock promotions are not supported */
1115 syslog(LOG_ERR, "jmapmbox: failed to grab write-lock"
1116 " on cached read-only mailbox %s", name);
1117 return IMAP_INTERNAL;
1118 }
1119 /* Found a cached mailbox. Increment refcount. */
1120 rec->refcount++;
1121 *mboxp = rec->mbox;
1122
1123 return 0;
1124 }
1125 }
1126
1127 /* Add mailbox to cache */
1128 if (req->force_openmbox_rw)
1129 rw = 1;
1130 r = rw ? mailbox_open_iwl(name, mboxp) : mailbox_open_irl(name, mboxp);
1131 if (r) {
1132 syslog(LOG_ERR, "jmap_openmbox(%s): %s", name, error_message(r));
1133 return r;
1134 }
1135 rec = xzmalloc(sizeof(struct _mboxcache_rec));
1136 rec->mbox = *mboxp;
1137 rec->refcount = 1;
1138 rec->rw = rw;
1139 ptrarray_add(req->mboxes, rec);
1140
1141 return 0;
1142 }
1143
jmap_isopenmbox(jmap_req_t * req,const char * name)1144 HIDDEN int jmap_isopenmbox(jmap_req_t *req, const char *name)
1145 {
1146
1147 int i;
1148 struct _mboxcache_rec *rec;
1149
1150 for (i = 0; i < req->mboxes->count; i++) {
1151 rec = (struct _mboxcache_rec*) ptrarray_nth(req->mboxes, i);
1152 if (!strcmp(name, rec->mbox->name))
1153 return 1;
1154 }
1155
1156 return 0;
1157 }
1158
jmap_closembox(jmap_req_t * req,struct mailbox ** mboxp)1159 HIDDEN void jmap_closembox(jmap_req_t *req, struct mailbox **mboxp)
1160 {
1161 struct _mboxcache_rec *rec = NULL;
1162 int i;
1163
1164 if (mboxp == NULL || *mboxp == NULL) return;
1165
1166 for (i = 0; i < req->mboxes->count; i++) {
1167 rec = (struct _mboxcache_rec*) ptrarray_nth(req->mboxes, i);
1168 if (rec->mbox == *mboxp) {
1169 if (!(--rec->refcount)) {
1170 ptrarray_remove(req->mboxes, i);
1171 mailbox_close(&rec->mbox);
1172 free(rec);
1173 }
1174 *mboxp = NULL;
1175 return;
1176 }
1177 }
1178 syslog(LOG_INFO, "jmap: ignoring non-cached mailbox %s", (*mboxp)->name);
1179 }
1180
jmap_set_blobid(const struct message_guid * guid,char * buf)1181 HIDDEN void jmap_set_blobid(const struct message_guid *guid, char *buf)
1182 {
1183 buf[0] = 'G';
1184 memcpy(buf+1, message_guid_encode(guid), 40);
1185 buf[41] = '\0';
1186 }
1187
jmap_set_emailid(const struct message_guid * guid,char * buf)1188 HIDDEN void jmap_set_emailid(const struct message_guid *guid, char *buf)
1189 {
1190 buf[0] = 'M';
1191 // appends NULL for us
1192 bin_to_lchex(&guid->value, 12, buf+1);
1193 }
1194
jmap_set_threadid(conversation_id_t cid,char * buf)1195 HIDDEN void jmap_set_threadid(conversation_id_t cid, char *buf)
1196 {
1197 buf[0] = 'T';
1198 memcpy(buf+1, conversation_id_encode(cid), 16);
1199 buf[17] = 0;
1200 }
1201
1202 struct findblob_data {
1203 jmap_req_t *req;
1204 const char *from_accountid;
1205 int is_shared_account;
1206 struct mailbox *mbox;
1207 msgrecord_t *mr;
1208 char *part_id;
1209 unsigned exact : 1;
1210 };
1211
findblob_cb(const conv_guidrec_t * rec,void * rock)1212 static int findblob_cb(const conv_guidrec_t *rec, void *rock)
1213 {
1214 struct findblob_data *d = (struct findblob_data*) rock;
1215 jmap_req_t *req = d->req;
1216 int r = 0;
1217
1218 if (d->exact) {
1219 // we only want top-level blobs
1220 if (rec->part) return 0;
1221 }
1222
1223 /* Check ACL */
1224 if (d->is_shared_account) {
1225 mbentry_t *mbentry = NULL;
1226 r = mboxlist_lookup(rec->mboxname, &mbentry, NULL);
1227 if (r) {
1228 syslog(LOG_ERR, "jmap_findblob: no mbentry for %s", rec->mboxname);
1229 return r;
1230 }
1231 int rights = jmap_myrights_mbentry(req, mbentry);
1232 mboxlist_entry_free(&mbentry);
1233 if ((rights & JACL_READITEMS) != JACL_READITEMS) {
1234 return 0;
1235 }
1236 }
1237
1238 r = jmap_openmbox(req, rec->mboxname, &d->mbox, 0);
1239 if (r) return r;
1240
1241 r = msgrecord_find(d->mbox, rec->uid, &d->mr);
1242 if (r) {
1243 jmap_closembox(req, &d->mbox);
1244 d->mr = NULL;
1245 return r;
1246 }
1247
1248 d->part_id = rec->part ? xstrdup(rec->part) : NULL;
1249 return IMAP_OK_COMPLETED;
1250 }
1251
_jmap_findblob(jmap_req_t * req,const char * from_accountid,const char * blobid,unsigned exact,struct mailbox ** mbox,msgrecord_t ** mr,struct body ** body,const struct body ** part,struct buf * blob)1252 static int _jmap_findblob(jmap_req_t *req, const char *from_accountid,
1253 const char *blobid, unsigned exact,
1254 struct mailbox **mbox, msgrecord_t **mr,
1255 struct body **body, const struct body **part,
1256 struct buf *blob)
1257 {
1258 const char *accountid = from_accountid ? from_accountid : req->accountid;
1259 struct findblob_data data = {
1260 req,
1261 /* from_accountid */
1262 accountid,
1263 /* is_shared_account */
1264 strcmp(req->userid, accountid),
1265 /* mbox */
1266 NULL,
1267 /* mr */
1268 NULL,
1269 /* part_id */
1270 NULL,
1271 exact
1272 };
1273 struct body *mybody = NULL;
1274 const struct body *mypart = NULL;
1275 int i, r;
1276 struct conversations_state *cstate, *mycstate = NULL;
1277
1278 syslog(LOG_DEBUG, "jmap_findblob (%s, %s)", from_accountid, blobid);
1279
1280 if (blob) {
1281 /* We check for an empty buf below, so we better start with one */
1282 buf_free(blob);
1283 }
1284
1285 if (!exact && blob && req->inmemory_blobs) {
1286 const struct buf *inmem = hash_lookup(blobid, req->inmemory_blobs);
1287 if (inmem) {
1288 buf_init_ro(blob, buf_base(inmem), buf_len(inmem));
1289 r = 0;
1290 goto done;
1291 }
1292 }
1293
1294 if (blobid[0] != 'G')
1295 return IMAP_NOTFOUND;
1296
1297 if (strcmp(req->accountid, accountid)) {
1298 cstate = conversations_get_user(accountid);
1299 if (!cstate) {
1300 r = conversations_open_user(accountid, 1/*shared*/, &mycstate);
1301 if (r) goto done;
1302
1303 cstate = mycstate;
1304 }
1305 }
1306 else {
1307 cstate = req->cstate;
1308 }
1309
1310 r = conversations_guid_foreach(cstate, blobid+1, findblob_cb, &data);
1311 if (r != IMAP_OK_COMPLETED) {
1312 if (!r) r = IMAP_NOTFOUND;
1313 goto done;
1314 }
1315
1316 /* Find part containing the data */
1317 if (data.part_id) {
1318 r = msgrecord_extract_bodystructure(data.mr, &mybody);
1319 if (r) goto done;
1320
1321 ptrarray_t parts = PTRARRAY_INITIALIZER;
1322 struct message_guid content_guid;
1323
1324 message_guid_decode(&content_guid, blobid+1);
1325
1326 ptrarray_push(&parts, mybody);
1327 while ((mypart = ptrarray_shift(&parts))) {
1328 if (!message_guid_cmp(&content_guid, &mypart->content_guid)) {
1329 break;
1330 }
1331 if (!mypart->subpart) {
1332 if (data.mbox->mbtype == MBTYPE_ADDRESSBOOK &&
1333 (mypart = jmap_contact_findblob(&content_guid, data.part_id,
1334 data.mbox, data.mr, blob))) {
1335 break;
1336 }
1337 continue;
1338 }
1339 ptrarray_push(&parts, mypart->subpart);
1340 for (i = 1; i < mypart->numparts; i++)
1341 ptrarray_push(&parts, mypart->subpart + i);
1342 }
1343 ptrarray_fini(&parts);
1344
1345 if (!mypart) {
1346 r = IMAP_NOTFOUND;
1347 goto done;
1348 }
1349 }
1350
1351 if (blob && !buf_base(blob)) {
1352 /* Map the message into memory */
1353 r = msgrecord_get_body(data.mr, blob);
1354 if (r) goto done;
1355 }
1356
1357 r = 0;
1358
1359 done:
1360 if (mycstate) {
1361 conversations_commit(&mycstate);
1362 }
1363 if (r) {
1364 if (data.mbox) jmap_closembox(req, &data.mbox);
1365 if (mybody) {
1366 message_free_body(mybody);
1367 free(mybody);
1368 }
1369 }
1370 else {
1371 *mbox = data.mbox;
1372 *mr = data.mr;
1373 if (part) *part = mypart;
1374 if (body) *body = mybody;
1375 else if (mybody) {
1376 message_free_body(mybody);
1377 free(mybody);
1378 }
1379 }
1380 if (data.part_id) free(data.part_id);
1381 return r;
1382 }
1383
jmap_findblob(jmap_req_t * req,const char * from_accountid,const char * blobid,struct mailbox ** mbox,msgrecord_t ** mr,struct body ** body,const struct body ** part,struct buf * blob)1384 HIDDEN int jmap_findblob(jmap_req_t *req, const char *from_accountid,
1385 const char *blobid,
1386 struct mailbox **mbox, msgrecord_t **mr,
1387 struct body **body, const struct body **part,
1388 struct buf *blob)
1389 {
1390 return _jmap_findblob(req, from_accountid, blobid, 0 /*exact*/,
1391 mbox, mr, body, part, blob);
1392 }
1393
1394 // we need to pass mbox so we can keep it open until the file has been used
jmap_findblob_exact(jmap_req_t * req,const char * from_accountid,const char * blobid,struct mailbox ** mbox,msgrecord_t ** mr,struct buf * blob)1395 HIDDEN int jmap_findblob_exact(jmap_req_t *req, const char *from_accountid,
1396 const char *blobid,
1397 struct mailbox **mbox, msgrecord_t **mr,
1398 struct buf *blob)
1399 {
1400 return _jmap_findblob(req, from_accountid, blobid, 1 /*exact*/,
1401 mbox, mr, NULL /*body*/, NULL /*part*/, blob);
1402 }
1403
jmap_cmpstate(jmap_req_t * req,json_t * state,int mbtype)1404 HIDDEN int jmap_cmpstate(jmap_req_t* req, json_t *state, int mbtype)
1405 {
1406 if (JNOTNULL(state)) {
1407 const char *s = json_string_value(state);
1408 if (!s) {
1409 return -1;
1410 }
1411 modseq_t client_modseq = atomodseq_t(s);
1412 modseq_t server_modseq = 0;
1413 switch (mbtype) {
1414 case MBTYPE_CALENDAR:
1415 server_modseq = req->counters.caldavmodseq;
1416 break;
1417 case MBTYPE_ADDRESSBOOK:
1418 server_modseq = req->counters.carddavmodseq;
1419 break;
1420 case MBTYPE_SUBMISSION:
1421 server_modseq = req->counters.submissionmodseq;
1422 break;
1423 default:
1424 server_modseq = req->counters.mailmodseq;
1425 }
1426 if (client_modseq < server_modseq)
1427 return -1;
1428 else if (client_modseq > server_modseq)
1429 return 1;
1430 else
1431 return 0;
1432 }
1433 return 0;
1434 }
1435
jmap_highestmodseq(jmap_req_t * req,int mbtype)1436 HIDDEN modseq_t jmap_highestmodseq(jmap_req_t *req, int mbtype)
1437 {
1438 modseq_t modseq;
1439
1440 /* Determine current counter by mailbox type. */
1441 switch (mbtype) {
1442 case MBTYPE_CALENDAR:
1443 modseq = req->counters.caldavmodseq;
1444 break;
1445 case MBTYPE_ADDRESSBOOK:
1446 modseq = req->counters.carddavmodseq;
1447 break;
1448 case MBTYPE_SUBMISSION:
1449 modseq = req->counters.submissionmodseq;
1450 break;
1451 case 0:
1452 modseq = req->counters.mailmodseq;
1453 break;
1454 default:
1455 modseq = req->counters.highestmodseq;
1456 }
1457
1458 return modseq;
1459 }
1460
jmap_getstate(jmap_req_t * req,int mbtype,int refresh)1461 HIDDEN json_t* jmap_getstate(jmap_req_t *req, int mbtype, int refresh)
1462 {
1463 char *inboxname = mboxname_user_mbox(req->accountid, NULL);
1464 if (refresh)
1465 assert (!mboxname_read_counters(inboxname, &req->counters));
1466 struct buf buf = BUF_INITIALIZER;
1467 json_t *state = NULL;
1468 modseq_t modseq = jmap_highestmodseq(req, mbtype);
1469
1470 buf_printf(&buf, MODSEQ_FMT, modseq);
1471 state = json_string(buf_cstring(&buf));
1472 buf_free(&buf);
1473
1474 free(inboxname);
1475 return state;
1476 }
1477
1478
jmap_fmtstate(modseq_t modseq)1479 HIDDEN json_t *jmap_fmtstate(modseq_t modseq)
1480 {
1481 struct buf buf = BUF_INITIALIZER;
1482 json_t *state = NULL;
1483 buf_printf(&buf, MODSEQ_FMT, modseq);
1484 state = json_string(buf_cstring(&buf));
1485 buf_free(&buf);
1486 return state;
1487 }
1488
jmap_xhref(const char * mboxname,const char * resource)1489 HIDDEN char *jmap_xhref(const char *mboxname, const char *resource)
1490 {
1491 /* XXX - look up root path from namespace? */
1492 struct buf buf = BUF_INITIALIZER;
1493 char *owner = mboxname_to_userid(mboxname);
1494
1495 const char *prefix = NULL;
1496 if (mboxname_isaddressbookmailbox(mboxname, 0)) {
1497 prefix = namespace_addressbook.prefix;
1498 }
1499 else if (mboxname_iscalendarmailbox(mboxname, 0)) {
1500 prefix = namespace_calendar.prefix;
1501 }
1502
1503 /* Path to home-set */
1504 buf_printf(&buf, "%s/%s/%s", prefix, USER_COLLECTION_PREFIX, httpd_userid);
1505 if (!strchr(httpd_userid, '@') && httpd_extradomain) {
1506 buf_printf(&buf, "@%s", httpd_extradomain);
1507 }
1508 buf_putc(&buf, '/');
1509
1510 if (strcmp(owner, httpd_userid)) {
1511 /* Encode shared collection as: <owner> "." <mboxname> */
1512 buf_appendcstr(&buf, owner);
1513 if (!strchr(owner, '@') && httpd_extradomain) {
1514 buf_printf(&buf, "@%s", httpd_extradomain);
1515 }
1516 buf_putc(&buf, SHARED_COLLECTION_DELIM);
1517 }
1518
1519 /* Collection */
1520 buf_printf(&buf, "%s", strrchr(mboxname, '.')+1);
1521
1522 if (resource)
1523 buf_printf(&buf, "/%s", resource);
1524 free(owner);
1525 return buf_release(&buf);
1526 }
1527
jmap_myrights_mbentry(jmap_req_t * req,const mbentry_t * mbentry)1528 HIDDEN int jmap_myrights_mbentry(jmap_req_t *req, const mbentry_t *mbentry)
1529 {
1530 return _rights_for_mbentry(req->authstate, mbentry, req->mbstates);
1531 }
1532
jmap_mbtype(jmap_req_t * req,const char * mboxname)1533 HIDDEN int jmap_mbtype(jmap_req_t *req, const char *mboxname)
1534 {
1535 struct mbstate *mbstate = hash_lookup(mboxname, req->mbstates);
1536 int mbtype;
1537
1538 if (!mbstate) {
1539 mbentry_t *mbentry = NULL;
1540 if (!jmap_mboxlist_lookup(mboxname, &mbentry, NULL)) {
1541 mbstate = _mbstate_getoradd(req->authstate, mbentry, req->mbstates);
1542 mbtype = mbstate->mbtype;
1543 }
1544 else mbtype = MBTYPE_UNKNOWN;
1545 mboxlist_entry_free(&mbentry);
1546 }
1547 else mbtype = mbstate->mbtype;
1548
1549 return mbtype;
1550 }
1551
1552 // gotta have them all
jmap_hasrights_mbentry(jmap_req_t * req,const mbentry_t * mbentry,int rights)1553 HIDDEN int jmap_hasrights_mbentry(jmap_req_t *req, const mbentry_t *mbentry, int rights)
1554 {
1555 int myrights = jmap_myrights_mbentry(req, mbentry);
1556 if ((myrights & rights) == rights) return 1;
1557 return 0;
1558 }
1559
jmap_myrights(jmap_req_t * req,const char * mboxname)1560 HIDDEN int jmap_myrights(jmap_req_t *req, const char *mboxname)
1561 {
1562 struct mbstate *mbstate = hash_lookup(mboxname, req->mbstates);
1563 if (mbstate) return mbstate->rights;
1564
1565 // if unable to read, that means no rights
1566 int rights = 0;
1567
1568 mbentry_t *mbentry = NULL;
1569 if (!jmap_mboxlist_lookup(mboxname, &mbentry, NULL)) {
1570 rights = _rights_for_mbentry(req->authstate, mbentry, req->mbstates);
1571 }
1572 mboxlist_entry_free(&mbentry);
1573
1574 return rights;
1575 }
1576
jmap_myrights_mboxid(jmap_req_t * req,const char * mboxid)1577 HIDDEN int jmap_myrights_mboxid(jmap_req_t *req, const char *mboxid)
1578 {
1579 int rights = 0;
1580 const mbentry_t *mbentry = jmap_mbentry_by_uniqueid(req, mboxid);
1581 if (mbentry) {
1582 rights = jmap_myrights_mbentry(req, mbentry);
1583 }
1584 return rights;
1585 }
1586
jmap_hasrights_mboxid(jmap_req_t * req,const char * mboxid,int rights)1587 HIDDEN int jmap_hasrights_mboxid(jmap_req_t *req, const char *mboxid, int rights)
1588 {
1589 const mbentry_t *mbentry = jmap_mbentry_by_uniqueid(req, mboxid);
1590 return mbentry ? jmap_hasrights_mbentry(req, mbentry, rights) : 0;
1591 }
1592
1593 // gotta have them all
jmap_hasrights(jmap_req_t * req,const char * mboxname,int rights)1594 HIDDEN int jmap_hasrights(jmap_req_t *req, const char *mboxname, int rights)
1595 {
1596 int myrights = jmap_myrights(req, mboxname);
1597 if ((myrights & rights) == rights) return 1;
1598 return 0;
1599 }
1600
jmap_myrights_delete(jmap_req_t * req,const char * mboxname)1601 HIDDEN void jmap_myrights_delete(jmap_req_t *req, const char *mboxname)
1602 {
1603 struct mbstate *mbstate = hash_del(mboxname, req->mbstates);
1604 free(mbstate);
1605 }
1606
1607 /* Add performance stats to method response */
jmap_add_perf(jmap_req_t * req,json_t * res)1608 static void jmap_add_perf(jmap_req_t *req, json_t *res)
1609 {
1610 struct rusage usage;
1611
1612 getrusage(RUSAGE_SELF, &usage);
1613
1614 json_t *perf = json_pack("{s:f s:f s:f}",
1615 "real", (now_ms() / 1000.0) - req->real_start,
1616 "user", timeval_get_double(&usage.ru_utime) - req->user_start,
1617 "sys", timeval_get_double(&usage.ru_stime) - req->sys_start);
1618 json_object_set(perf, "details", req->perf_details); // incref
1619
1620 json_object_set_new(res, "performance", perf);
1621 }
1622
jmap_ok(jmap_req_t * req,json_t * res)1623 HIDDEN void jmap_ok(jmap_req_t *req, json_t *res)
1624 {
1625 json_object_set_new(res, "accountId", json_string(req->accountid));
1626
1627 json_t *item = json_pack("[]");
1628 json_array_append_new(item, json_string(req->method));
1629 json_array_append_new(item, res);
1630 json_array_append_new(item, json_string(req->tag));
1631 json_array_append_new(req->response, item);
1632
1633 if (jmap_is_using(req, JMAP_PERFORMANCE_EXTENSION))
1634 jmap_add_perf(req, res);
1635 }
1636
jmap_error(jmap_req_t * req,json_t * err)1637 HIDDEN void jmap_error(jmap_req_t *req, json_t *err)
1638 {
1639 json_array_append_new(req->response,
1640 json_pack("[s,o,s]", "error", err, req->tag));
1641 }
1642
1643
jmap_parse_strings(json_t * arg,struct jmap_parser * parser,const char * prop)1644 HIDDEN int jmap_parse_strings(json_t *arg,
1645 struct jmap_parser *parser, const char *prop)
1646 {
1647 if (!json_is_array(arg)) {
1648 jmap_parser_invalid(parser, prop);
1649 return 0;
1650 }
1651 int valid = 1;
1652 size_t i;
1653 json_t *val;
1654 json_array_foreach(arg, i, val) {
1655 if (!json_is_string(val)) {
1656 jmap_parser_push_index(parser, prop, i, NULL);
1657 jmap_parser_invalid(parser, NULL);
1658 jmap_parser_pop(parser);
1659 valid = 0;
1660 }
1661 }
1662 return valid;
1663 }
1664
1665
jmap_property_find(const char * name,const jmap_property_t props[])1666 HIDDEN const jmap_property_t *jmap_property_find(const char *name,
1667 const jmap_property_t props[])
1668 {
1669 const jmap_property_t *prop;
1670
1671 for (prop = props; prop && prop->name; prop++) {
1672 if (!strcmp(name, prop->name)) return prop;
1673 else {
1674 size_t len = strlen(prop->name);
1675 if ((prop->name[len-1] == '*') && !strncmp(name, prop->name, len-1))
1676 return prop;
1677 }
1678 }
1679
1680 return NULL;
1681 }
1682
1683
1684 /* Foo/get */
1685
jmap_get_parse(jmap_req_t * req,struct jmap_parser * parser,const jmap_property_t valid_props[],int allow_null_ids,jmap_args_parse_cb args_parse,void * args_rock,struct jmap_get * get,json_t ** err)1686 HIDDEN void jmap_get_parse(jmap_req_t *req,
1687 struct jmap_parser *parser,
1688 const jmap_property_t valid_props[],
1689 int allow_null_ids,
1690 jmap_args_parse_cb args_parse,
1691 void *args_rock,
1692 struct jmap_get *get,
1693 json_t **err)
1694 {
1695 json_t *jargs = req->args;
1696 const char *key;
1697 json_t *arg, *val;
1698 size_t i;
1699
1700 memset(get, 0, sizeof(struct jmap_get));
1701
1702 get->list = json_array();
1703 get->not_found = json_array();
1704
1705 json_object_foreach(jargs, key, arg) {
1706 if (!strcmp(key, "accountId")) {
1707 /* already handled in jmap_api() */
1708 }
1709
1710 else if (!strcmp(key, "ids")) {
1711 if (json_is_array(arg)) {
1712 get->ids = json_array();
1713 /* JMAP spec requires: "If an identical id is included
1714 * more than once in the request, the server MUST only
1715 * include it once in either the list or notFound
1716 * argument of the response."
1717 * So let's weed out duplicate ids here. */
1718 hash_table _dedup = HASH_TABLE_INITIALIZER;
1719 construct_hash_table(&_dedup, json_array_size(arg) + 1, 0);
1720 json_array_foreach(arg, i, val) {
1721 const char *id = json_string_value(val);
1722 if (!id) {
1723 jmap_parser_push_index(parser, "ids", i, NULL);
1724 jmap_parser_invalid(parser, NULL);
1725 jmap_parser_pop(parser);
1726 continue;
1727 }
1728 /* Weed out unknown creation ids and add the ids of known
1729 * creation ids to the requested ids list. THis might
1730 * cause a race if the Foo object pointed to by creation
1731 * id is deleted between parsing the request and answering
1732 * it. But re-checking creation ids for their existence
1733 * later in the control flow just shifts the problem */
1734 if (*id == '#') {
1735 const char *id2 = jmap_lookup_id(req, id + 1);
1736 if (!id2) {
1737 json_array_append_new(get->not_found,
1738 json_string(id));
1739 continue;
1740 }
1741 id = id2;
1742 }
1743 if (hash_lookup(id, &_dedup)) {
1744 continue;
1745 }
1746 json_array_append_new(get->ids, json_string(id));
1747 }
1748 free_hash_table(&_dedup, NULL);
1749 }
1750 else if (JNOTNULL(arg)) {
1751 jmap_parser_invalid(parser, "ids");
1752 }
1753 }
1754
1755 else if (!strcmp(key, "properties")) {
1756 if (json_is_array(arg)) {
1757 get->props = xzmalloc(sizeof(hash_table));
1758 construct_hash_table(get->props, json_array_size(arg) + 1, 0);
1759 json_array_foreach(arg, i, val) {
1760 const char *name = json_string_value(val);
1761 const jmap_property_t *propdef = NULL;
1762 if (name) {
1763 propdef = jmap_property_find(name, valid_props);
1764 if (propdef && propdef->capability &&
1765 !jmap_is_using(req, propdef->capability)) {
1766 propdef = NULL;
1767 }
1768 }
1769 if (!propdef) {
1770 jmap_parser_push_index(parser, "properties", i, name);
1771 jmap_parser_invalid(parser, NULL);
1772 jmap_parser_pop(parser);
1773 continue;
1774 }
1775 hash_insert(name, (void*)1, get->props);
1776 }
1777 }
1778 else if (JNOTNULL(arg)) {
1779 jmap_parser_invalid(parser, "properties");
1780 }
1781 }
1782
1783 else if (!args_parse || !args_parse(req, parser, key, arg, args_rock)) {
1784 jmap_parser_invalid(parser, key);
1785 }
1786 }
1787
1788 if (json_array_size(parser->invalid)) {
1789 *err = json_pack("{s:s s:O}", "type", "invalidArguments",
1790 "arguments", parser->invalid);
1791 return;
1792 }
1793
1794 if (!allow_null_ids && !JNOTNULL(get->ids)) {
1795 *err = json_pack("{s:s, s:s}", "type", "requestTooLarge",
1796 "description", "ids must be specified");
1797 return;
1798 }
1799
1800 if (*err) return;
1801
1802 if (get->props == NULL) {
1803 /* Initialize default properties */
1804 int nvalid = 0;
1805 const jmap_property_t *prop;
1806 for (prop = valid_props; prop && prop->name; prop++) {
1807 nvalid++;
1808 }
1809 get->props = xzmalloc(sizeof(hash_table));
1810 construct_hash_table(get->props, nvalid + 1, 0);
1811 for (prop = valid_props; prop && prop->name; prop++) {
1812 if (prop->flags & JMAP_PROP_SKIP_GET) {
1813 continue;
1814 }
1815 if (!prop->capability || jmap_is_using(req, prop->capability)) {
1816 hash_insert(prop->name, (void*)1, get->props);
1817 }
1818 }
1819 }
1820 else {
1821 const jmap_property_t *prop;
1822 for (prop = valid_props; prop && prop->name; prop++) {
1823 if (prop->flags & JMAP_PROP_ALWAYS_GET) {
1824 if (!hash_lookup(prop->name, get->props)) {
1825 hash_insert(prop->name, (void*)1, get->props);
1826 }
1827 }
1828 }
1829 }
1830
1831 /* Number of ids checked in validate_request() */
1832 }
1833
jmap_get_fini(struct jmap_get * get)1834 HIDDEN void jmap_get_fini(struct jmap_get *get)
1835 {
1836 free_hash_table(get->props, NULL);
1837 free(get->props);
1838 free(get->state);
1839 json_decref(get->ids);
1840 json_decref(get->list);
1841 json_decref(get->not_found);
1842 }
1843
jmap_get_reply(struct jmap_get * get)1844 HIDDEN json_t *jmap_get_reply(struct jmap_get *get)
1845 {
1846 json_t *res = json_object();
1847 json_object_set_new(res, "state", json_string(get->state));
1848 json_object_set(res, "list", get->list);
1849 json_object_set(res, "notFound", get->not_found);
1850 return res;
1851 }
1852
1853
1854 /* Foo/set */
1855
jmap_set_validate_props(jmap_req_t * req,const char * id,json_t * jobj,const jmap_property_t valid_props[],json_t ** err)1856 static void jmap_set_validate_props(jmap_req_t *req, const char *id, json_t *jobj,
1857 const jmap_property_t valid_props[],
1858 json_t **err)
1859 {
1860 json_t *invalid = json_array();
1861 const char *path;
1862 json_t *jval;
1863
1864 json_object_foreach(jobj, path, jval) {
1865 /* Determine property name */
1866 const char *pname = path;
1867 char *tmp = NULL;
1868 const char *slash = strchr(pname, '/');
1869 if (slash) {
1870 tmp = jmap_pointer_decode(pname, slash - path);
1871 if (tmp) pname = tmp;
1872 }
1873 /* Validate against property spec */
1874 const jmap_property_t *prop = jmap_property_find(pname, valid_props);
1875 if (!prop) {
1876 json_array_append_new(invalid, json_string(path));
1877 }
1878 else if (prop->capability && !jmap_is_using(req, prop->capability)) {
1879 json_array_append_new(invalid, json_string(path));
1880 }
1881 else if (id) {
1882 /* update */
1883 if (!strcmp("id", prop->name) &&
1884 strcmpnull(id, json_string_value(jval))) {
1885 /* can NEVER change id */
1886 json_array_append_new(invalid, json_string(path));
1887 }
1888 /* XXX could check IMMUTABLE and SERVER_SET here, but we can't
1889 * reject such properties if they match the current value */
1890 }
1891 else {
1892 /* create */
1893 if (prop->flags & JMAP_PROP_SERVER_SET) {
1894 json_array_append_new(invalid, json_string(path));
1895 }
1896 }
1897 if (tmp) free(tmp);
1898 }
1899 if (json_array_size(invalid)) {
1900 *err = json_pack("{s:s s:o}",
1901 "type", "invalidProperties",
1902 "properties", invalid);
1903 }
1904 else {
1905 json_decref(invalid);
1906 }
1907 }
1908
jmap_set_parse(jmap_req_t * req,struct jmap_parser * parser,const jmap_property_t valid_props[],jmap_args_parse_cb args_parse,void * args_rock,struct jmap_set * set,json_t ** err)1909 HIDDEN void jmap_set_parse(jmap_req_t *req, struct jmap_parser *parser,
1910 const jmap_property_t valid_props[],
1911 jmap_args_parse_cb args_parse, void *args_rock,
1912 struct jmap_set *set, json_t **err)
1913 {
1914 json_t *jargs = req->args;
1915 memset(set, 0, sizeof(struct jmap_set));
1916 set->create = json_object();
1917 set->update = json_object();
1918 set->destroy = json_array();
1919 set->created = json_object();
1920 set->updated = json_object();
1921 set->destroyed = json_array();
1922 set->not_created = json_object();
1923 set->not_updated = json_object();
1924 set->not_destroyed = json_object();
1925
1926 const char *key;
1927 json_t *arg, *val;
1928
1929 json_object_foreach(jargs, key, arg) {
1930 if (!strcmp(key, "accountId")) {
1931 /* already handled in jmap_api() */
1932 }
1933
1934 /* ifInState */
1935 else if (!strcmp(key, "ifInState")) {
1936 if (json_is_string(arg)) {
1937 set->if_in_state = json_string_value(arg);
1938 }
1939 else if (JNOTNULL(arg)) {
1940 jmap_parser_invalid(parser, "ifInState");
1941 }
1942 }
1943
1944 /* create */
1945 else if (!strcmp(key, "create")) {
1946 if (json_is_object(arg)) {
1947 const char *id;
1948 json_object_foreach(arg, id, val) {
1949 if (!json_is_object(val)) {
1950 jmap_parser_push(parser, "create");
1951 jmap_parser_invalid(parser, id);
1952 jmap_parser_pop(parser);
1953 continue;
1954 }
1955 json_object_set(set->create, id, val);
1956 }
1957 }
1958 else if (JNOTNULL(arg)) {
1959 jmap_parser_invalid(parser, "create");
1960 }
1961 }
1962
1963 /* update */
1964 else if (!strcmp(key, "update")) {
1965 if (json_is_object(arg)) {
1966 const char *id;
1967 json_object_foreach(arg, id, val) {
1968 if (!json_is_object(val)) {
1969 jmap_parser_push(parser, "update");
1970 jmap_parser_invalid(parser, id);
1971 jmap_parser_pop(parser);
1972 continue;
1973 }
1974 json_object_set(set->update, id, val);
1975 }
1976 }
1977 else if (JNOTNULL(arg)) {
1978 jmap_parser_invalid(parser, "update");
1979 }
1980 }
1981
1982 /* destroy */
1983 else if (!strcmp(key, "destroy")) {
1984 if (JNOTNULL(arg)) {
1985 jmap_parse_strings(arg, parser, "destroy");
1986 if (!json_array_size(parser->invalid)) {
1987 json_decref(set->destroy);
1988 set->destroy = json_incref(arg);
1989 }
1990 }
1991 }
1992
1993 else if (!args_parse || !args_parse(req, parser, key, arg, args_rock)) {
1994 jmap_parser_invalid(parser, key);
1995 }
1996 }
1997
1998 if (json_array_size(parser->invalid)) {
1999 *err = json_pack("{s:s s:O}", "type", "invalidArguments",
2000 "arguments", parser->invalid);
2001 }
2002
2003 if (valid_props) {
2004 json_t *jval;
2005 /* Make sure no property is set without its capability */
2006 json_object_foreach(json_object_get(jargs, "create"), key, jval) {
2007 json_t *err = NULL;
2008 jmap_set_validate_props(req, NULL, jval, valid_props, &err);
2009 if (err) {
2010 json_object_del(set->create, key);
2011 json_object_set_new(set->not_created, key, err);
2012 }
2013 }
2014 json_object_foreach(json_object_get(jargs, "update"), key, jval) {
2015 json_t *err = NULL;
2016 jmap_set_validate_props(req, key, jval, valid_props, &err);
2017 if (err) {
2018 json_object_del(set->update, key);
2019 json_object_set_new(set->not_updated, key, err);
2020 }
2021 }
2022 // TODO We could report the following set errors here:
2023 // -invalidPatch
2024 // - willDestroy
2025 }
2026 }
2027
2028
jmap_set_fini(struct jmap_set * set)2029 HIDDEN void jmap_set_fini(struct jmap_set *set)
2030 {
2031 free(set->old_state);
2032 free(set->new_state);
2033 json_decref(set->create);
2034 json_decref(set->update);
2035 json_decref(set->destroy);
2036 json_decref(set->created);
2037 json_decref(set->updated);
2038 json_decref(set->destroyed);
2039 json_decref(set->not_created);
2040 json_decref(set->not_updated);
2041 json_decref(set->not_destroyed);
2042 }
2043
jmap_set_reply(struct jmap_set * set)2044 HIDDEN json_t *jmap_set_reply(struct jmap_set *set)
2045 {
2046 json_t *res = json_object();
2047 json_object_set_new(res, "oldState",
2048 set->old_state ? json_string(set->old_state) : json_null());
2049 json_object_set_new(res, "newState", json_string(set->new_state));
2050 json_object_set(res, "created", json_object_size(set->created) ?
2051 set->created : json_null());
2052 json_object_set(res, "updated", json_object_size(set->updated) ?
2053 set->updated : json_null());
2054 json_object_set(res, "destroyed", json_array_size(set->destroyed) ?
2055 set->destroyed : json_null());
2056 json_object_set(res, "notCreated", json_object_size(set->not_created) ?
2057 set->not_created : json_null());
2058 json_object_set(res, "notUpdated", json_object_size(set->not_updated) ?
2059 set->not_updated : json_null());
2060 json_object_set(res, "notDestroyed", json_object_size(set->not_destroyed) ?
2061 set->not_destroyed : json_null());
2062 return res;
2063 }
2064
2065
2066 /* Foo/changes */
2067
jmap_changes_parse(jmap_req_t * req,struct jmap_parser * parser,modseq_t minmodseq,jmap_args_parse_cb args_parse,void * args_rock,struct jmap_changes * changes,json_t ** err)2068 HIDDEN void jmap_changes_parse(jmap_req_t *req,
2069 struct jmap_parser *parser,
2070 modseq_t minmodseq,
2071 jmap_args_parse_cb args_parse,
2072 void *args_rock,
2073 struct jmap_changes *changes,
2074 json_t **err)
2075 {
2076 json_t *jargs = req->args;
2077 const char *key;
2078 json_t *arg;
2079 int have_sincemodseq = 0;
2080
2081 memset(changes, 0, sizeof(struct jmap_changes));
2082 changes->created = json_array();
2083 changes->updated = json_array();
2084 changes->destroyed = json_array();
2085
2086 json_object_foreach(jargs, key, arg) {
2087 if (!strcmp(key, "accountId")) {
2088 /* already handled in jmap_api() */
2089 }
2090
2091 /* sinceState */
2092 else if (!strcmp(key, "sinceState")) {
2093 if (json_is_string(arg) && imparse_isnumber(json_string_value(arg))) {
2094 have_sincemodseq = 1;
2095 changes->since_modseq = atomodseq_t(json_string_value(arg));
2096 }
2097 else {
2098 jmap_parser_invalid(parser, "sinceState");
2099 }
2100 }
2101
2102 /* maxChanges */
2103 else if (!strcmp(key, "maxChanges")) {
2104 if (json_is_integer(arg) && json_integer_value(arg) > 0) {
2105 changes->max_changes = json_integer_value(arg);
2106 } else if (JNOTNULL(arg)) {
2107 jmap_parser_invalid(parser, "maxChanges");
2108 }
2109 }
2110
2111 else if (!args_parse || !args_parse(req, parser, key, arg, args_rock)) {
2112 jmap_parser_invalid(parser, key);
2113 }
2114 }
2115
2116 if (json_array_size(parser->invalid)) {
2117 *err = json_pack("{s:s s:O}", "type", "invalidArguments",
2118 "arguments", parser->invalid);
2119 }
2120 else if (!have_sincemodseq) {
2121 *err = json_pack("{s:s s:s}", "type", "cannotCalculateChanges",
2122 "description", "no sinceModseq");
2123 }
2124 else if (changes->since_modseq < minmodseq) {
2125 *err = json_pack("{s:s s:s}", "type", "cannotCalculateChanges",
2126 "description", "outdated sinceModseq");
2127 }
2128 }
2129
jmap_changes_fini(struct jmap_changes * changes)2130 HIDDEN void jmap_changes_fini(struct jmap_changes *changes)
2131 {
2132 json_decref(changes->created);
2133 json_decref(changes->updated);
2134 json_decref(changes->destroyed);
2135 }
2136
jmap_changes_reply(struct jmap_changes * changes)2137 HIDDEN json_t *jmap_changes_reply(struct jmap_changes *changes)
2138 {
2139 json_t *res = json_object();
2140 json_object_set_new(res, "oldState", jmap_fmtstate(changes->since_modseq));
2141 json_object_set_new(res, "newState", jmap_fmtstate(changes->new_modseq));
2142 json_object_set_new(res, "hasMoreChanges",
2143 json_boolean(changes->has_more_changes));
2144 json_object_set(res, "created", changes->created);
2145 json_object_set(res, "updated", changes->updated);
2146 json_object_set(res, "destroyed", changes->destroyed);
2147 return res;
2148 }
2149
2150
2151 /* Foo/copy */
2152
jmap_copy_parse(jmap_req_t * req,struct jmap_parser * parser,jmap_args_parse_cb args_parse,void * args_rock,struct jmap_copy * copy,json_t ** err)2153 HIDDEN void jmap_copy_parse(jmap_req_t *req, struct jmap_parser *parser,
2154 jmap_args_parse_cb args_parse, void *args_rock,
2155 struct jmap_copy *copy, json_t **err)
2156 {
2157 json_t *jargs = req->args;
2158
2159 memset(copy, 0, sizeof(struct jmap_copy));
2160 copy->blob_copy = !strcmp(req->method, "Blob/copy");
2161 copy->create = copy->blob_copy ? json_array() : json_object();
2162 copy->created = json_object();
2163 copy->not_created = json_object();
2164
2165 const char *key;
2166 json_t *arg;
2167
2168 json_object_foreach(jargs, key, arg) {
2169 /* fromAccountId */
2170 if (!strcmp(key, "fromAccountId")) {
2171 if (json_is_string(arg)) {
2172 copy->from_account_id = json_string_value(arg);
2173 }
2174 else if (JNOTNULL(arg)) {
2175 jmap_parser_invalid(parser, "fromAccountId");
2176 }
2177 }
2178
2179 /* accountId */
2180 else if (!strcmp(key, "accountId")) {
2181 /* JMAP request parser already set it */
2182 assert(req->accountid);
2183 continue;
2184 }
2185
2186 /* blobIds */
2187 else if (copy->blob_copy &&
2188 !strcmp(key, "blobIds") && json_is_array(arg)) {
2189 struct buf buf = BUF_INITIALIZER;
2190 json_t *id;
2191 size_t i;
2192 json_array_foreach(arg, i, id) {
2193 if (!json_is_string(id)) {
2194 buf_printf(&buf, "blobIds[%zu]", i);
2195 jmap_parser_invalid(parser, buf_cstring(&buf));
2196 buf_reset(&buf);
2197 }
2198 else json_array_append(copy->create, id);
2199 }
2200 }
2201
2202 /* create */
2203 else if (!copy->blob_copy &&
2204 !strcmp(key, "create") && json_is_object(arg)) {
2205 jmap_parser_push(parser, "create");
2206 const char *creation_id;
2207 json_t *obj;
2208 json_object_foreach(arg, creation_id, obj) {
2209 if (!json_is_object(obj)) {
2210 jmap_parser_invalid(parser, creation_id);
2211 }
2212 else if (!json_is_string(json_object_get(obj, "id"))) {
2213 jmap_parser_push(parser, creation_id);
2214 jmap_parser_invalid(parser, "id");
2215 jmap_parser_pop(parser);
2216 }
2217 else json_object_set(copy->create, creation_id, obj);
2218 }
2219 jmap_parser_pop(parser);
2220 }
2221
2222 /* onSuccessDestroyOriginal */
2223 else if (!copy->blob_copy && !strcmp(key, "onSuccessDestroyOriginal") &&
2224 json_is_boolean(arg)) {
2225 copy->on_success_destroy_original = json_boolean_value(arg);
2226 }
2227
2228 else if (!args_parse || !args_parse(req, parser, key, arg, args_rock)) {
2229 jmap_parser_invalid(parser, key);
2230 }
2231 }
2232
2233 if (json_array_size(parser->invalid)) {
2234 *err = json_pack("{s:s s:O}", "type", "invalidArguments",
2235 "arguments", parser->invalid);
2236 }
2237
2238 if (!req->accountid || !copy->from_account_id ||
2239 !strcmp(req->accountid, copy->from_account_id)) {
2240 *err = json_pack("{s:s s:[s,s]}", "type", "invalidArguments",
2241 "arguments", "accountId", "fromAccountId");
2242 }
2243 }
2244
2245
jmap_copy_fini(struct jmap_copy * copy)2246 HIDDEN void jmap_copy_fini(struct jmap_copy *copy)
2247 {
2248 json_decref(copy->create);
2249 json_decref(copy->created);
2250 json_decref(copy->not_created);
2251 }
2252
jmap_copy_reply(struct jmap_copy * copy)2253 HIDDEN json_t *jmap_copy_reply(struct jmap_copy *copy)
2254 {
2255 json_t *res = json_object();
2256 json_object_set_new(res, "fromAccountId",
2257 json_string(copy->from_account_id));
2258 json_object_set(res, copy->blob_copy ? "copied" : "created",
2259 json_object_size(copy->created) ?
2260 copy->created : json_null());
2261 json_object_set(res, copy->blob_copy ? "notCopied" : "notCreated",
2262 json_object_size(copy->not_created) ?
2263 copy->not_created : json_null());
2264 return res;
2265 }
2266
2267
2268 /* Foo/query */
2269
jmap_buildfilter(json_t * arg,jmap_buildfilter_cb * parse)2270 HIDDEN jmap_filter *jmap_buildfilter(json_t *arg, jmap_buildfilter_cb *parse)
2271 {
2272 jmap_filter *f = (jmap_filter *) xzmalloc(sizeof(struct jmap_filter));
2273 int pe;
2274 const char *val;
2275 int iscond = 1;
2276
2277 /* operator */
2278 pe = jmap_readprop(arg, "operator", 0 /*mandatory*/, NULL, "s", &val);
2279 if (pe > 0) {
2280 if (!strncmp("AND", val, 3)) {
2281 f->op = JMAP_FILTER_OP_AND;
2282 } else if (!strncmp("OR", val, 2)) {
2283 f->op = JMAP_FILTER_OP_OR;
2284 } else if (!strncmp("NOT", val, 3)) {
2285 f->op = JMAP_FILTER_OP_NOT;
2286 }
2287 }
2288 iscond = f->op == JMAP_FILTER_OP_NONE;
2289
2290 /* conditions */
2291 json_t *conds = json_object_get(arg, "conditions");
2292 if (conds && !iscond && json_array_size(conds)) {
2293 size_t i, n_conditions = json_array_size(conds);
2294 for (i = 0; i < n_conditions; i++) {
2295 json_t *cond = json_array_get(conds, i);
2296 ptrarray_push(&f->conditions, jmap_buildfilter(cond, parse));
2297 }
2298 }
2299
2300 if (iscond) {
2301 ptrarray_push(&f->conditions, parse(arg));
2302 }
2303
2304 return f;
2305 }
2306
jmap_filter_match(jmap_filter * f,jmap_filtermatch_cb * match,void * rock)2307 HIDDEN int jmap_filter_match(jmap_filter *f,
2308 jmap_filtermatch_cb *match, void *rock)
2309 {
2310 if (f->op == JMAP_FILTER_OP_NONE) {
2311 return match(ptrarray_head(&f->conditions), rock);
2312 } else {
2313 int i;
2314 for (i = 0; i < ptrarray_size(&f->conditions); i++) {
2315 int m = jmap_filter_match(ptrarray_nth(&f->conditions, i), match, rock);
2316 if (m && f->op == JMAP_FILTER_OP_OR) {
2317 return 1;
2318 } else if (m && f->op == JMAP_FILTER_OP_NOT) {
2319 return 0;
2320 } else if (!m && f->op == JMAP_FILTER_OP_AND) {
2321 return 0;
2322 }
2323 }
2324 return f->op == JMAP_FILTER_OP_AND || f->op == JMAP_FILTER_OP_NOT;
2325 }
2326 }
2327
jmap_filter_free(jmap_filter * f,jmap_filterfree_cb * freecond)2328 HIDDEN void jmap_filter_free(jmap_filter *f, jmap_filterfree_cb *freecond)
2329 {
2330 void *cond;
2331
2332 while ((cond = ptrarray_pop(&f->conditions))) {
2333 if (f->op == JMAP_FILTER_OP_NONE) {
2334 if (freecond) freecond(cond);
2335 }
2336 else {
2337 jmap_filter_free(cond, freecond);
2338 }
2339 }
2340 ptrarray_fini(&f->conditions);
2341 free(f);
2342 }
2343
jmap_filter_parse(jmap_req_t * req,struct jmap_parser * parser,json_t * filter,json_t * unsupported,jmap_filter_parse_cb parse_condition,void * cond_rock,json_t ** err)2344 HIDDEN void jmap_filter_parse(jmap_req_t *req, struct jmap_parser *parser,
2345 json_t *filter, json_t *unsupported,
2346 jmap_filter_parse_cb parse_condition, void *cond_rock,
2347 json_t **err)
2348 {
2349 json_t *arg, *val;
2350 const char *s;
2351 size_t i;
2352
2353 if (err && *err) return;
2354
2355 if (!JNOTNULL(filter) || json_typeof(filter) != JSON_OBJECT) {
2356 jmap_parser_invalid(parser, NULL);
2357 return;
2358 }
2359 arg = json_object_get(filter, "operator");
2360 if ((s = json_string_value(arg))) {
2361 if (strcmp("AND", s) && strcmp("OR", s) && strcmp("NOT", s)) {
2362 jmap_parser_invalid(parser, "operator");
2363 }
2364 arg = json_object_get(filter, "conditions");
2365 if (!json_array_size(arg)) {
2366 jmap_parser_invalid(parser, "conditions");
2367 }
2368 json_array_foreach(arg, i, val) {
2369 jmap_parser_push_index(parser, "conditions", i, NULL);
2370 jmap_filter_parse(req, parser, val, unsupported, parse_condition, cond_rock, err);
2371 jmap_parser_pop(parser);
2372 }
2373 } else if (arg) {
2374 jmap_parser_invalid(parser, "operator");
2375 } else {
2376 parse_condition(req, parser, filter, unsupported, cond_rock, err);
2377 }
2378 }
2379
jmap_comparator_parse(jmap_req_t * req,struct jmap_parser * parser,json_t * jsort,json_t * unsupported,jmap_comparator_parse_cb comp_cb,void * comp_rock,json_t ** err)2380 HIDDEN void jmap_comparator_parse(jmap_req_t *req, struct jmap_parser *parser,
2381 json_t *jsort, json_t *unsupported,
2382 jmap_comparator_parse_cb comp_cb, void *comp_rock,
2383 json_t **err)
2384 {
2385 if (!json_is_object(jsort)) {
2386 jmap_parser_invalid(parser, NULL);
2387 return;
2388 }
2389
2390 struct jmap_comparator comp = { NULL, 0, NULL };
2391
2392 /* property */
2393 json_t *val = json_object_get(jsort, "property");
2394 comp.property = json_string_value(val);
2395 if (!comp.property) {
2396 jmap_parser_invalid(parser, "property");
2397 }
2398
2399 /* isAscending */
2400 comp.is_ascending = 1;
2401 val = json_object_get(jsort, "isAscending");
2402 if (JNOTNULL(val)) {
2403 if (!json_is_boolean(val)) {
2404 jmap_parser_invalid(parser, "isAscending");
2405 }
2406 comp.is_ascending = json_boolean_value(val);
2407 }
2408
2409 /* collation */
2410 val = json_object_get(jsort, "collation");
2411 if (JNOTNULL(val) && !json_is_string(val)) {
2412 jmap_parser_invalid(parser, "collation");
2413 }
2414 comp.collation = json_string_value(val);
2415
2416
2417 if (comp.property && !comp_cb(req, &comp, comp_rock, err)) {
2418 struct buf buf = BUF_INITIALIZER;
2419 json_array_append_new(unsupported,
2420 json_string(jmap_parser_path(parser, &buf)));
2421 buf_free(&buf);
2422 }
2423 }
2424
jmap_query_parse(jmap_req_t * req,struct jmap_parser * parser,jmap_args_parse_cb args_parse,void * args_rock,jmap_filter_parse_cb filter_cb,void * filter_rock,jmap_comparator_parse_cb comp_cb,void * comp_rock,struct jmap_query * query,json_t ** err)2425 HIDDEN void jmap_query_parse(jmap_req_t *req, struct jmap_parser *parser,
2426 jmap_args_parse_cb args_parse, void *args_rock,
2427 jmap_filter_parse_cb filter_cb, void *filter_rock,
2428 jmap_comparator_parse_cb comp_cb, void *comp_rock,
2429 struct jmap_query *query, json_t **err)
2430 {
2431 json_t *jargs = req->args;
2432 const char *key;
2433 json_t *arg, *val;
2434 size_t i;
2435
2436 memset(query, 0, sizeof(struct jmap_query));
2437 query->ids = json_array();
2438
2439 json_t *unsupported_filter = json_array();
2440 json_t *unsupported_sort = json_array();
2441
2442 json_object_foreach(jargs, key, arg) {
2443 if (!strcmp(key, "accountId")) {
2444 /* already handled in jmap_api() */
2445 }
2446
2447 /* filter */
2448 else if (!strcmp(key, "filter")) {
2449 if (json_is_object(arg)) {
2450 jmap_parser_push(parser, "filter");
2451 jmap_filter_parse(req, parser, arg, unsupported_filter,
2452 filter_cb, filter_rock, err);
2453 jmap_parser_pop(parser);
2454 query->filter = arg;
2455 if (err && *err) {
2456 goto done;
2457 }
2458 }
2459 else if (JNOTNULL(arg)) {
2460 jmap_parser_invalid(parser, "filter");
2461 }
2462 }
2463
2464 /* sort */
2465 else if (!strcmp(key, "sort")) {
2466 if (json_is_array(arg)) {
2467 json_array_foreach(arg, i, val) {
2468 jmap_parser_push_index(parser, "sort", i, NULL);
2469 jmap_comparator_parse(req, parser, val, unsupported_sort,
2470 comp_cb, comp_rock, err);
2471 jmap_parser_pop(parser);
2472 if (err && *err) {
2473 goto done;
2474 }
2475 }
2476 if (json_array_size(arg)) {
2477 query->sort = arg;
2478 }
2479 }
2480 else if (JNOTNULL(arg)) {
2481 jmap_parser_invalid(parser, "sort");
2482 }
2483 }
2484
2485 else if (!strcmp(key, "position")) {
2486 if (json_is_integer(arg)) {
2487 query->position = json_integer_value(arg);
2488 }
2489 else if (arg) {
2490 jmap_parser_invalid(parser, "position");
2491 }
2492 }
2493
2494 else if (!strcmp(key, "anchor")) {
2495 if (json_is_string(arg)) {
2496 query->anchor = json_string_value(arg);
2497 } else if (JNOTNULL(arg)) {
2498 jmap_parser_invalid(parser, "anchor");
2499 }
2500 }
2501
2502 else if (!strcmp(key, "anchorOffset")) {
2503 if (json_is_integer(arg)) {
2504 query->anchor_offset = json_integer_value(arg);
2505 } else if (JNOTNULL(arg)) {
2506 jmap_parser_invalid(parser, "anchorOffset");
2507 }
2508 }
2509
2510 else if (!strcmp(key, "limit")) {
2511 if (json_is_integer(arg) && json_integer_value(arg) >= 0) {
2512 query->limit = json_integer_value(arg);
2513 query->have_limit = 1;
2514 } else if (JNOTNULL(arg)) {
2515 jmap_parser_invalid(parser, "limit");
2516 }
2517 }
2518
2519 else if (!strcmp(key, "calculateTotal")) {
2520 if (json_is_boolean(arg)) {
2521 query->calculate_total = json_boolean_value(arg);
2522 } else if (JNOTNULL(arg)) {
2523 jmap_parser_invalid(parser, "calculateTotal");
2524 }
2525 }
2526
2527 else if (!args_parse || !args_parse(req, parser, key, arg, args_rock)) {
2528 jmap_parser_invalid(parser, key);
2529 }
2530 }
2531
2532 if (json_array_size(parser->invalid)) {
2533 *err = json_pack("{s:s s:O}", "type", "invalidArguments",
2534 "arguments", parser->invalid);
2535 }
2536 else if (json_array_size(unsupported_filter)) {
2537 *err = json_pack("{s:s s:O}", "type", "unsupportedFilter",
2538 "filters", unsupported_filter);
2539 }
2540 else if (json_array_size(unsupported_sort)) {
2541 *err = json_pack("{s:s s:O}", "type", "unsupportedSort",
2542 "sort", unsupported_sort);
2543 }
2544
2545 done:
2546 json_decref(unsupported_filter);
2547 json_decref(unsupported_sort);
2548 }
2549
jmap_query_fini(struct jmap_query * query)2550 HIDDEN void jmap_query_fini(struct jmap_query *query)
2551 {
2552 free(query->query_state);
2553 json_decref(query->ids);
2554 }
2555
jmap_query_reply(struct jmap_query * query)2556 HIDDEN json_t *jmap_query_reply(struct jmap_query *query)
2557 {
2558
2559 json_t *res = json_object();
2560 json_object_set(res, "filter", query->filter);
2561 json_object_set(res, "sort", query->sort);
2562 json_object_set_new(res, "queryState", json_string(query->query_state));
2563 json_object_set_new(res, "canCalculateChanges",
2564 json_boolean(query->can_calculate_changes));
2565 json_object_set_new(res, "position", json_integer(query->result_position));
2566 json_object_set_new(res, "total", json_integer(query->total));
2567 /* Special case total */
2568 if (query->position > 0 && query->total && query->total < SSIZE_MAX) {
2569 if (query->position > (ssize_t) query->total) {
2570 json_decref(query->ids);
2571 query->ids = json_array();
2572 }
2573 }
2574 /* Special case limit 0 */
2575 if (query->have_limit && query->limit == 0) {
2576 json_array_clear(query->ids);
2577 }
2578 /* Special case clamped limit */
2579 if (query->server_limit) {
2580 json_object_set_new(res, "limit", json_integer(query->server_limit));
2581 }
2582
2583 json_object_set(res, "ids", query->ids);
2584 return res;
2585 }
2586
2587
2588 /* Foo/queryChanges */
2589
jmap_querychanges_parse(jmap_req_t * req,struct jmap_parser * parser,jmap_args_parse_cb args_parse,void * args_rock,jmap_filter_parse_cb filter_cb,void * filter_rock,jmap_comparator_parse_cb comp_cb,void * comp_rock,struct jmap_querychanges * query,json_t ** err)2590 HIDDEN void jmap_querychanges_parse(jmap_req_t *req,
2591 struct jmap_parser *parser,
2592 jmap_args_parse_cb args_parse, void *args_rock,
2593 jmap_filter_parse_cb filter_cb, void *filter_rock,
2594 jmap_comparator_parse_cb comp_cb, void *comp_rock,
2595 struct jmap_querychanges *query,
2596 json_t **err)
2597 {
2598 json_t *jargs = req->args;
2599 const char *key;
2600 json_t *arg, *val;
2601 size_t i;
2602
2603 memset(query, 0, sizeof(struct jmap_querychanges));
2604 query->removed = json_array();
2605 query->added = json_array();
2606
2607 json_t *unsupported_filter = json_array();
2608 json_t *unsupported_sort = json_array();
2609
2610 json_object_foreach(jargs, key, arg) {
2611 if (!strcmp(key, "accountId")) {
2612 /* already handled in jmap_api() */
2613 }
2614
2615 /* filter */
2616 else if (!strcmp(key, "filter")) {
2617 if (json_is_object(arg)) {
2618 jmap_parser_push(parser, "filter");
2619 jmap_filter_parse(req, parser, arg, unsupported_filter,
2620 filter_cb, filter_rock, err);
2621 jmap_parser_pop(parser);
2622 query->filter = arg;
2623 if (err && *err) {
2624 goto done;
2625 }
2626 }
2627 else if (JNOTNULL(arg)) {
2628 jmap_parser_invalid(parser, "filter");
2629 }
2630 }
2631
2632 /* sort */
2633 else if (!strcmp(key, "sort")) {
2634 if (json_is_array(arg)) {
2635 json_array_foreach(arg, i, val) {
2636 jmap_parser_push_index(parser, "sort", i, NULL);
2637 jmap_comparator_parse(req, parser, val, unsupported_sort,
2638 comp_cb, comp_rock, err);
2639 jmap_parser_pop(parser);
2640 }
2641 if (json_array_size(arg)) {
2642 query->sort = arg;
2643 }
2644 }
2645 else if (JNOTNULL(arg)) {
2646 jmap_parser_invalid(parser, "sort");
2647 }
2648 }
2649
2650 /* sinceQueryState */
2651 else if (!strcmp(key, "sinceQueryState")) {
2652 if (json_is_string(arg)) {
2653 query->since_querystate = json_string_value(arg);
2654 } else {
2655 jmap_parser_invalid(parser, "sinceQueryState");
2656 }
2657 }
2658
2659 /* maxChanges */
2660 else if (!strcmp(key, "maxChanges")) {
2661 if (json_is_integer(arg) && json_integer_value(arg) > 0) {
2662 query->max_changes = json_integer_value(arg);
2663 } else if (JNOTNULL(arg)) {
2664 jmap_parser_invalid(parser, "maxChanges");
2665 }
2666 }
2667
2668 /* upToId */
2669 else if (!strcmp(key, "upToId")) {
2670 if (json_is_string(arg)) {
2671 query->up_to_id = json_string_value(arg);
2672 } else if (JNOTNULL(arg)) {
2673 jmap_parser_invalid(parser, "upToId");
2674 }
2675 }
2676
2677 /* calculateTotal */
2678 else if (!strcmp(key, "calculateTotal")) {
2679 if (json_is_boolean(arg)) {
2680 query->calculate_total = json_boolean_value(arg);
2681 } else if (JNOTNULL(arg)) {
2682 jmap_parser_invalid(parser, "calculateTotal");
2683 }
2684 }
2685
2686 else if (!args_parse || !args_parse(req, parser, key, arg, args_rock)) {
2687 jmap_parser_invalid(parser, key);
2688 }
2689 }
2690
2691 if (query->since_querystate == NULL) {
2692 jmap_parser_invalid(parser, "sinceQueryState");
2693 }
2694
2695 if (json_array_size(parser->invalid)) {
2696 *err = json_pack("{s:s s:O}", "type", "invalidArguments",
2697 "arguments", parser->invalid);
2698 }
2699 else if (json_array_size(unsupported_filter)) {
2700 *err = json_pack("{s:s s:O}", "type", "unsupportedFilter",
2701 "filters", unsupported_filter);
2702 }
2703 else if (json_array_size(unsupported_sort)) {
2704 *err = json_pack("{s:s s:O}", "type", "unsupportedSort",
2705 "sort", unsupported_sort);
2706 }
2707
2708 done:
2709 json_decref(unsupported_filter);
2710 json_decref(unsupported_sort);
2711 }
2712
jmap_querychanges_fini(struct jmap_querychanges * query)2713 HIDDEN void jmap_querychanges_fini(struct jmap_querychanges *query)
2714 {
2715 free(query->new_querystate);
2716 json_decref(query->removed);
2717 json_decref(query->added);
2718 }
2719
jmap_querychanges_reply(struct jmap_querychanges * query)2720 HIDDEN json_t *jmap_querychanges_reply(struct jmap_querychanges *query)
2721 {
2722 json_t *res = json_object();
2723 json_object_set(res, "filter", query->filter);
2724 json_object_set(res, "sort", query->sort);
2725 json_object_set_new(res, "oldQueryState",
2726 json_string(query->since_querystate));
2727 json_object_set_new(res, "newQueryState",
2728 json_string(query->new_querystate));
2729 json_object_set_new(res, "upToId", query->up_to_id ?
2730 json_string(query->up_to_id) : json_null());
2731 json_object_set(res, "removed", query->removed);
2732 json_object_set(res, "added", query->added);
2733 json_object_set_new(res, "total", json_integer(query->total));
2734 return res;
2735 }
2736
2737
2738 /* Foo/parse */
2739
jmap_parse_parse(jmap_req_t * req,struct jmap_parser * parser,jmap_args_parse_cb args_parse,void * args_rock,struct jmap_parse * parse,json_t ** err)2740 HIDDEN void jmap_parse_parse(jmap_req_t *req,
2741 struct jmap_parser *parser,
2742 jmap_args_parse_cb args_parse,
2743 void *args_rock,
2744 struct jmap_parse *parse,
2745 json_t **err)
2746 {
2747 json_t *jargs = req->args;
2748 const char *key;
2749 json_t *arg;
2750
2751 memset(parse, 0, sizeof(struct jmap_parse));
2752
2753 parse->parsed = json_object();
2754 parse->not_parsable = json_array();
2755 parse->not_found = json_array();
2756
2757 json_object_foreach(jargs, key, arg) {
2758 if (!strcmp(key, "accountId")) {
2759 /* already handled in jmap_api() */
2760 }
2761
2762 else if (!strcmp(key, "blobIds")) {
2763 jmap_parse_strings(arg, parser, "blobIds");
2764 parse->blob_ids = arg;
2765 }
2766
2767 else if (!args_parse || !args_parse(req, parser, key, arg, args_rock)) {
2768 jmap_parser_invalid(parser, key);
2769 }
2770 }
2771
2772 if (json_array_size(parser->invalid)) {
2773 *err = json_pack("{s:s s:O}", "type", "invalidArguments",
2774 "arguments", parser->invalid);
2775 }
2776 }
2777
jmap_parse_fini(struct jmap_parse * parse)2778 HIDDEN void jmap_parse_fini(struct jmap_parse *parse)
2779 {
2780 json_decref(parse->parsed);
2781 json_decref(parse->not_parsable);
2782 json_decref(parse->not_found);
2783 }
2784
jmap_parse_reply(struct jmap_parse * parse)2785 HIDDEN json_t *jmap_parse_reply(struct jmap_parse *parse)
2786 {
2787 json_t *res = json_object();
2788
2789 if (json_object_size(parse->parsed))
2790 json_object_set(res, "parsed", parse->parsed);
2791 else
2792 json_object_set_new(res, "parsed", json_null());
2793 if (json_array_size(parse->not_parsable))
2794 json_object_set(res, "notParsable", parse->not_parsable);
2795 else
2796 json_object_set_new(res, "notParsable", json_null());
2797 if (json_array_size(parse->not_found))
2798 json_object_set(res, "notFound", parse->not_found);
2799 else
2800 json_object_set_new(res, "notFound", json_null());
2801 return res;
2802 }
2803
2804
_json_has(int rights,int need)2805 static json_t *_json_has(int rights, int need)
2806 {
2807 return (((rights & need) == need) ? json_true() : json_false());
2808 }
2809
jmap_get_sharewith(const mbentry_t * mbentry)2810 HIDDEN json_t *jmap_get_sharewith(const mbentry_t *mbentry)
2811 {
2812 char *aclstr = xstrdupnull(mbentry->acl);
2813 char *owner = mboxname_to_userid(mbentry->name);
2814 int iscalendar = (mbentry->mbtype & MBTYPE_CALENDAR);
2815
2816 json_t *sharewith = json_null();
2817
2818 char *userid;
2819 char *nextid;
2820 for (userid = aclstr; userid; userid = nextid) {
2821 int rights;
2822 char *rightstr;
2823
2824 rightstr = strchr(userid, '\t');
2825 if (!rightstr) break;
2826 *rightstr++ = '\0';
2827
2828 nextid = strchr(rightstr, '\t');
2829 if (!nextid) break;
2830 *nextid++ = '\0';
2831
2832 cyrus_acl_strtomask(rightstr, &rights);
2833
2834 // skip system users and owner
2835 if (is_system_user(userid)) continue;
2836 if (!strcmp(userid, owner)) continue;
2837
2838 // we've got one! Create the object if this is the first
2839 if (!JNOTNULL(sharewith))
2840 sharewith = json_pack("{}");
2841
2842 json_t *obj = json_pack("{}");
2843 json_object_set_new(sharewith, userid, obj);
2844
2845 if (iscalendar)
2846 json_object_set_new(obj, "mayReadFreeBusy",
2847 _json_has(rights, JACL_READFB));
2848 json_object_set_new(obj, "mayRead",
2849 _json_has(rights, JACL_READITEMS));
2850 json_object_set_new(obj, "mayWrite",
2851 _json_has(rights, JACL_WRITE));
2852 json_object_set_new(obj, "mayAdmin",
2853 _json_has(rights, JACL_ADMIN));
2854 }
2855
2856 free(aclstr);
2857 free(owner);
2858
2859 return sharewith;
2860 }
2861
2862 struct acl_item {
2863 unsigned int mayAdmin:1;
2864 unsigned int mayWrite:1;
2865 unsigned int mayPost:1;
2866 unsigned int mayRead:1;
2867 unsigned int mayReadFreeBusy:1;
2868 };
2869
2870 struct acl_change {
2871 struct acl_item old;
2872 struct acl_item new;
2873 };
2874
2875 struct invite_rock {
2876 xmlNodePtr notify;
2877 xmlNsPtr ns[NUM_NAMESPACE];
2878 const char *owner;
2879 const char *mboxname;
2880 struct buf resource;
2881 struct request_target_t tgt;
2882 const struct prop_entry *live_props;
2883 };
2884
access_from_acl_item(struct acl_item * item)2885 static unsigned access_from_acl_item(struct acl_item *item)
2886 {
2887 unsigned access = 0;
2888
2889 if (item->mayReadFreeBusy)
2890 access |= JACL_READFB;
2891 if (item->mayRead)
2892 access |= JACL_READITEMS|JACL_SETSEEN;
2893 if (item->mayWrite)
2894 access |= JACL_WRITE;
2895 if (item->mayPost)
2896 access |= JACL_SUBMIT;
2897 if (item->mayAdmin)
2898 access |= JACL_ADMIN|JACL_RENAME;
2899
2900 return access;
2901 }
2902
2903 /* Create and send a sharing invite */
send_dav_invite(const char * userid,void * val,void * rock)2904 static void send_dav_invite(const char *userid, void *val, void *rock)
2905 {
2906 struct acl_change *change = (struct acl_change *) val;
2907 struct invite_rock *irock = (struct invite_rock *) rock;
2908 long old = access_from_acl_item(&change->old);
2909 long new = access_from_acl_item(&change->new);
2910
2911 if (old != new) {
2912 int access, r = 0;
2913
2914 if (!new) access = SHARE_NONE;
2915 else if (change->new.mayWrite) access = SHARE_READWRITE;
2916 else access = SHARE_READONLY;
2917
2918 if (!old || !new) {
2919 /* Change subscription */
2920 r = mboxlist_changesub(irock->mboxname, userid, httpd_authstate,
2921 access != SHARE_NONE, 0, /*notify*/1);
2922 }
2923
2924 if (!r) {
2925 static const char *displayname_annot =
2926 DAV_ANNOT_NS "<" XML_NS_DAV ">displayname";
2927 struct buf buf = BUF_INITIALIZER;
2928 r = annotatemore_lookupmask(irock->mboxname, displayname_annot,
2929 irock->owner, &buf);
2930 /* Fall back to last part of mailbox name */
2931 if (r || !buf_len(&buf)) {
2932 buf_setcstr(&buf, strrchr(irock->mboxname, '.') + 1);
2933 }
2934 r = dav_create_invite(&irock->notify, irock->ns, &irock->tgt,
2935 irock->live_props, userid, access,
2936 BAD_CAST buf_cstring(&buf));
2937 buf_free(&buf);
2938 }
2939 if (!r) {
2940 /* Create a resource name for the notifications -
2941 We use a consistent naming scheme so that multiple
2942 notifications of the same type for the same resource
2943 are coalesced (overwritten) */
2944 buf_reset(&irock->resource);
2945 buf_printf(&irock->resource, "%x-%x-%x-%x.xml",
2946 strhash(XML_NS_DAV),
2947 strhash(SHARE_INVITE_NOTIFICATION),
2948 strhash(irock->tgt.mbentry->name),
2949 strhash(userid));
2950
2951 r = dav_send_notification(irock->notify->doc,
2952 userid, buf_cstring(&irock->resource));
2953 }
2954 }
2955 }
2956
add_useracls(const char * userid,void * val,void * rock)2957 static void add_useracls(const char *userid, void *val, void *rock)
2958 {
2959 struct acl_change *change = val;
2960 char **aclptr = rock;
2961
2962 unsigned access = access_from_acl_item(&change->new);
2963
2964 if (access)
2965 cyrus_acl_set(aclptr, userid, ACL_MODE_SET, access, NULL, NULL);
2966 }
2967
2968 struct shared_rock {
2969 hash_table *user_access;
2970 const char *owner;
2971 const char *upload_mboxname;
2972 };
2973
sharedrights_cb(const mbentry_t * mbentry,void * vrock)2974 static int sharedrights_cb(const mbentry_t *mbentry, void *vrock)
2975 {
2976 struct shared_rock *srock = (struct shared_rock *) vrock;
2977 const char *userid;
2978 char *nextid = NULL;
2979
2980 /* skip any special use folders */
2981 if (mbentry->mbtype &&
2982 !(mbentry->mbtype & (MBTYPE_CALENDAR | MBTYPE_ADDRESSBOOK))) {
2983 return 0;
2984 }
2985 /* make sure we skip the upload folder itself */
2986 else if (!strcmp(mbentry->name, srock->upload_mboxname)) {
2987 return 0;
2988 }
2989
2990 /* parse the existing ACL and add to the sum of rights for each user */
2991 for (userid = mbentry->acl; userid; userid = nextid) {
2992 char *rightstr;
2993 int access;
2994
2995 rightstr = strchr(userid, '\t');
2996 if (!rightstr) break;
2997 *rightstr++ = '\0';
2998
2999 nextid = strchr(rightstr, '\t');
3000 if (!nextid) break;
3001 *nextid++ = '\0';
3002
3003 cyrus_acl_strtomask(rightstr, &access);
3004
3005 /* remove any scheduling ACLs */
3006 access &= ~DACL_SCHED;
3007
3008 if (strcmp(userid, srock->owner) && !is_system_user(userid)) {
3009 /* limit ACL to JMAP sharing rights */
3010 access &= (JACL_READITEMS | JACL_WRITE);
3011 }
3012
3013 if (access) {
3014 access |= (uintptr_t) hash_lookup(userid, srock->user_access);
3015
3016 hash_insert(userid, (void *)((uintptr_t)access), srock->user_access);
3017 }
3018 }
3019
3020 return 0;
3021 }
3022
add_shareacls(const char * userid,void * val,void * rock)3023 static void add_shareacls(const char *userid, void *val, void *rock)
3024 {
3025 char **aclptr = rock;
3026 int access = (uintptr_t) val;
3027
3028 if (access & JACL_WRITE)
3029 cyrus_acl_set(aclptr, userid, ACL_MODE_SET, access, NULL, NULL);
3030 else
3031 cyrus_acl_remove(aclptr, userid, NULL, NULL);
3032 }
3033
set_upload_rights(const char * accountid)3034 static int set_upload_rights(const char *accountid)
3035 {
3036 /* XXX This is currently done by brute force.
3037 We could be smarter by only doing a full scan
3038 iff r/w is removed for a userid. */
3039 struct mailbox *mbox = NULL;
3040 int r = jmap_open_upload_collection(accountid, &mbox);
3041
3042 if (r) return r;
3043
3044 hash_table user_access = HASH_TABLE_INITIALIZER;
3045 struct shared_rock srock = { &user_access, accountid, mbox->name };
3046
3047 /* build the sum of the shared rights for each each user */
3048 construct_hash_table(&user_access, 64, 0);
3049 mboxlist_usermboxtree(accountid, NULL, &sharedrights_cb, &srock, 0);
3050
3051 /* create the ACL for the upload folder */
3052 char *newacl = xstrdup(""); /* start with empty ACL */
3053 hash_enumerate_sorted(&user_access, add_shareacls, &newacl, cmpstringp_raw);
3054 free_hash_table(&user_access, NULL);
3055
3056 /* ok, change the mailboxes database */
3057 r = mboxlist_sync_setacls(mbox->name, newacl, mailbox_modseq_dirty(mbox));
3058 if (r) {
3059 syslog(LOG_ERR, "mboxlist_sync_setacls(%s) failed: %s",
3060 mbox->name, error_message(r));
3061 }
3062 else {
3063 /* ok, change the backup in cyrus.header */
3064 r = mailbox_set_acl(mbox, newacl);
3065 if (r) {
3066 syslog(LOG_ERR, "mailbox_set_acl(%s) failed: %s",
3067 mbox->name, error_message(r));
3068 }
3069 }
3070
3071 mailbox_close(&mbox);
3072
3073 return 0;
3074 }
3075
jmap_set_sharewith(struct mailbox * mbox,json_t * shareWith,int overwrite)3076 HIDDEN int jmap_set_sharewith(struct mailbox *mbox,
3077 json_t *shareWith, int overwrite)
3078 {
3079 hash_table user_access = HASH_TABLE_INITIALIZER;
3080 int isdav = (mbox->mbtype & MBTYPES_DAV);
3081 int iscalendar = (mbox->mbtype & MBTYPE_CALENDAR);
3082 char *owner = mboxname_to_userid(mbox->name);
3083 char *acl = xstrdup(mbox->acl);
3084 struct acl_change *change;
3085 const char *userid;
3086 json_t *rights;
3087 int r;
3088 char *newacl = xstrdup(""); /* start with empty ACL */
3089
3090 if (json_is_null(shareWith)) overwrite = 1;
3091
3092 construct_hash_table(&user_access, 64, 0);
3093
3094 /* parse the existing ACL and calculate the types of shares */
3095 char *nextid = NULL;
3096 for (userid = acl; userid; userid = nextid) {
3097 char *rightstr;
3098 int access;
3099
3100 rightstr = strchr(userid, '\t');
3101 if (!rightstr) break;
3102 *rightstr++ = '\0';
3103
3104 nextid = strchr(rightstr, '\t');
3105 if (!nextid) break;
3106 *nextid++ = '\0';
3107
3108 /* Is this a shareable user? (not owner or admin) */
3109 if (strcmp(userid, owner) && !is_system_user(userid)) {
3110 int oldrights;
3111 cyrus_acl_strtomask(rightstr, &oldrights);
3112
3113 /* Add regular user to our table */
3114 change = xzmalloc(sizeof(struct acl_change));
3115
3116 if (oldrights & JACL_READFB)
3117 change->old.mayReadFreeBusy = 1;
3118 if (oldrights & JACL_READITEMS)
3119 change->old.mayRead = 1;
3120 if ((oldrights & JACL_WRITE) == JACL_WRITE)
3121 change->old.mayWrite = 1;
3122 if (oldrights & JACL_ADMIN)
3123 change->old.mayAdmin = 1;
3124 if (isdav) change->old.mayPost = change->old.mayWrite;
3125
3126 /* unless we're overwriting, we start with the existing state */
3127 if (!overwrite) change->new = change->old;
3128
3129 hash_insert(userid, (void *) change, &user_access);
3130 }
3131 else {
3132 /* Add owner or system user to new ACL */
3133 cyrus_acl_strtomask(rightstr, &access);
3134
3135 r = cyrus_acl_set(&newacl, userid,
3136 ACL_MODE_SET, access, NULL, NULL);
3137 if (r) {
3138 syslog(LOG_ERR, "cyrus_acl_set(%s, %s) failed: %s",
3139 mbox->name, userid, error_message(r));
3140 goto done;
3141 }
3142 }
3143 }
3144
3145 /* Patch the ACL from shareWith */
3146 json_object_foreach(shareWith, userid, rights) {
3147 const char *right;
3148 json_t *val;
3149
3150 /* Validate user id and rights */
3151 if (!(strlen(userid) && rights &&
3152 (json_is_object(rights) || json_is_null(rights)))) {
3153 continue;
3154 }
3155
3156 /* skip system users and owner */
3157 if (is_system_user(userid)) continue;
3158 if (!strcmp(userid, owner)) continue;
3159
3160 change = hash_lookup(userid, &user_access);
3161 if (!change) {
3162 change = xzmalloc(sizeof(struct acl_change));
3163 hash_insert(userid, (void *) change, &user_access);
3164 }
3165
3166 if (json_is_null(rights)) {
3167 /* remove user from ACL */
3168 struct acl_item zero = {0,0,0,0,0};
3169 if (change) change->new = zero;
3170 }
3171 else {
3172 /* accumulate rights be granted and denied */
3173 json_object_foreach(rights, right, val) {
3174 unsigned set = json_boolean_value(val);
3175
3176 if (!strcmp(right, "mayAdmin"))
3177 change->new.mayAdmin = set;
3178 else if (!strcmp(right, "mayWrite"))
3179 change->new.mayWrite = set;
3180 else if (!strcmp(right, "mayRead"))
3181 change->new.mayRead = set;
3182 else if (iscalendar && !strcmp(right, "mayReadFreeBusy"))
3183 change->new.mayReadFreeBusy = set;
3184 }
3185 if (isdav) change->new.mayPost = change->new.mayWrite;
3186 }
3187 }
3188
3189 /* add all the users back to the share ACL */
3190 hash_enumerate_sorted(&user_access, add_useracls, &newacl, cmpstringp_raw);
3191
3192 /* ok, change the mailboxes database */
3193 r = mboxlist_sync_setacls(mbox->name, newacl, mailbox_modseq_dirty(mbox));
3194 if (r) {
3195 syslog(LOG_ERR, "mboxlist_sync_setacls(%s) failed: %s",
3196 mbox->name, error_message(r));
3197 }
3198 else {
3199 /* ok, change the backup in cyrus.header */
3200 r = mailbox_set_acl(mbox, newacl);
3201 if (r) {
3202 syslog(LOG_ERR, "mailbox_set_acl(%s) failed: %s",
3203 mbox->name, error_message(r));
3204 }
3205 }
3206
3207 if (!r) {
3208 /* Set proper access rights on JMAP upload folder */
3209 r = set_upload_rights(owner);
3210 }
3211
3212 if (!r && isdav) {
3213 /* Send sharing invites */
3214 struct invite_rock irock;
3215 struct meth_params *pparams;
3216 mbname_t *mbname;
3217 const char *errstr = NULL;
3218
3219 memset(&irock, 0, sizeof(struct invite_rock));
3220 irock.owner = owner;
3221
3222 /* Find the DAV namespace for this mailbox */
3223 if (iscalendar)
3224 irock.tgt.namespace = &namespace_calendar;
3225 else if (mbox->mbtype & MBTYPE_ADDRESSBOOK)
3226 irock.tgt.namespace = &namespace_addressbook;
3227 else
3228 irock.tgt.namespace = &namespace_drive;
3229
3230 /* Get "live" properties for the namespace */
3231 pparams = irock.tgt.namespace->methods[METH_PROPFIND].params;
3232 irock.live_props = pparams->propfind.lprops;
3233
3234 /* Create DAV URL for this collection */
3235 mbname = mbname_from_intname(mbox->name);
3236 if (!mbname_domain(mbname)) mbname_set_domain(mbname, httpd_extradomain);
3237
3238 make_collection_url(&irock.resource, irock.tgt.namespace->prefix,
3239 /*haszzzz*/0, mbname, mbname_userid(mbname));
3240
3241 /* Create a request target for this collection */
3242 irock.tgt.flags = TGT_DAV_SHARED; // prevent old-style sharing redirect
3243 r = pparams->parse_path(buf_cstring(&irock.resource), &irock.tgt, &errstr);
3244
3245 if (!r) {
3246 /* Process each user */
3247 irock.mboxname = mbox->name;
3248 hash_enumerate(&user_access, send_dav_invite, &irock);
3249 }
3250
3251 /* Cleanup */
3252 if (irock.notify) xmlFreeDoc(irock.notify->doc);
3253 mboxlist_entry_free(&irock.tgt.mbentry);
3254 free(irock.tgt.userid);
3255 buf_free(&irock.resource);
3256 mbname_free(&mbname);
3257 }
3258
3259 done:
3260 free_hash_table(&user_access, &free);
3261 free(owner);
3262 free(newacl);
3263 free(acl);
3264
3265 return r;
3266 }
3267
jmap_parse_sharewith_patch(json_t * arg,json_t ** shareWith)3268 HIDDEN void jmap_parse_sharewith_patch(json_t *arg, json_t **shareWith)
3269 {
3270 struct buf buf = BUF_INITIALIZER;
3271 const char *field = NULL;
3272 json_t *jval;
3273
3274 json_object_foreach(arg, field, jval) {
3275 if (!strncmp(field, "shareWith/", 10)) {
3276 const char *userid = field + 10;
3277 const char *right = strchr(userid, '/');
3278
3279 if (!*shareWith) *shareWith = json_object();
3280
3281 if (right) {
3282 /* individual right */
3283 buf_setmap(&buf, userid, right - userid);
3284 userid = buf_cstring(&buf);
3285
3286 json_t *rights = json_object_get(*shareWith, userid);
3287 if (rights) {
3288 /* add to existing ShareRights for this userid */
3289 json_object_set(rights, right+1, jval);
3290 }
3291 else {
3292 /* create new ShareRights for this userid */
3293 json_object_set_new(*shareWith, userid,
3294 json_pack("{s:o}", right+1, jval));
3295 }
3296 }
3297 else {
3298 /* complete ShareRights */
3299 json_object_set(*shareWith, userid, jval);
3300 }
3301 }
3302 }
3303
3304 buf_free(&buf);
3305 }
3306
jmap_is_using(jmap_req_t * req,const char * capa)3307 HIDDEN int jmap_is_using(jmap_req_t *req, const char *capa)
3308 {
3309 return strarray_find(req->using_capabilities, capa, 0) >= 0;
3310 }
3311
3312 /*
3313 * Lookup 'name' in the mailbox list, ignoring reserved/deleted records
3314 */
jmap_mboxlist_lookup(const char * name,mbentry_t ** entryptr,struct txn ** tid)3315 HIDDEN int jmap_mboxlist_lookup(const char *name,
3316 mbentry_t **entryptr, struct txn **tid)
3317 {
3318 mbentry_t *entry = NULL;
3319 int r;
3320
3321 r = mboxlist_lookup_allow_all(name, &entry, tid);
3322
3323 if (r) return r;
3324
3325 /* Ignore "reserved" entries, like they aren't there */
3326 if (entry->mbtype & MBTYPE_RESERVE) {
3327 mboxlist_entry_free(&entry);
3328 return IMAP_MAILBOX_RESERVED;
3329 }
3330
3331 /* Ignore "deleted" entries, like they aren't there */
3332 if (entry->mbtype & MBTYPE_DELETED) {
3333 mboxlist_entry_free(&entry);
3334 return IMAP_MAILBOX_NONEXISTENT;
3335 }
3336
3337 if (entryptr) *entryptr = entry;
3338 else mboxlist_entry_free(&entry);
3339
3340 return 0;
3341 }
3342
_mbentry_by_uniqueid_cb(const mbentry_t * mbentry,void * rock)3343 static int _mbentry_by_uniqueid_cb(const mbentry_t *mbentry, void *rock)
3344 {
3345 struct hash_table *hash = rock;
3346 hash_insert(mbentry->uniqueid, mboxlist_entry_copy(mbentry), hash);
3347 return 0;
3348 }
3349
jmap_mbentry_by_uniqueid(jmap_req_t * req,const char * id)3350 EXPORTED const mbentry_t *jmap_mbentry_by_uniqueid(jmap_req_t *req, const char *id)
3351 {
3352 if (!req->mbentry_byid) {
3353 req->mbentry_byid = xzmalloc(sizeof(struct hash_table));
3354 construct_hash_table(req->mbentry_byid, 1024, 0);
3355 mboxlist_usermboxtree(req->accountid, req->authstate,
3356 _mbentry_by_uniqueid_cb, req->mbentry_byid,
3357 MBOXTREE_INTERMEDIATES);
3358 }
3359
3360 return (const mbentry_t *)hash_lookup(id, req->mbentry_byid);
3361 }
3362
jmap_mbentry_by_uniqueid_copy(jmap_req_t * req,const char * id)3363 EXPORTED mbentry_t *jmap_mbentry_by_uniqueid_copy(jmap_req_t *req, const char *id)
3364 {
3365 const mbentry_t *mbentry = jmap_mbentry_by_uniqueid(req, id);
3366 if (!mbentry) return NULL;
3367 return mboxlist_entry_copy(mbentry);
3368 }
3369
_free_mbentry(void * rock)3370 static void _free_mbentry(void *rock)
3371 {
3372 mbentry_t *entry = rock;
3373 mboxlist_entry_free(&entry);
3374 }
3375
jmap_mbentry_cache_free(jmap_req_t * req)3376 EXPORTED void jmap_mbentry_cache_free(jmap_req_t *req)
3377 {
3378 if (req->mbentry_byid) {
3379 free_hash_table(req->mbentry_byid, _free_mbentry);
3380 free(req->mbentry_byid);
3381 req->mbentry_byid = NULL;
3382 }
3383 }
3384