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