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