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