1 /* jmap_core.c -- Routines for handling JMAP Core requests
2  *
3  * Copyright (c) 1994-2019 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 <errno.h>
47 
48 #include <string.h>
49 #include <syslog.h>
50 
51 #include "acl.h"
52 #include "append.h"
53 #include "http_jmap.h"
54 #include "times.h"
55 
56 /* generated headers are not necessarily in current directory */
57 #include "imap/http_err.h"
58 #include "imap/imap_err.h"
59 #include "imap/jmap_err.h"
60 
61 
62 /* JMAP Core API Methods */
63 static int jmap_blob_copy(jmap_req_t *req);
64 static int jmap_core_echo(jmap_req_t *req);
65 
66 /* JMAP extension methods */
67 static int jmap_blob_get(jmap_req_t *req);
68 static int jmap_blob_set(jmap_req_t *req);
69 static int jmap_quota_get(jmap_req_t *req);
70 static int jmap_usercounters_get(jmap_req_t *req);
71 
72 jmap_method_t jmap_core_methods_standard[] = {
73     {
74         "Blob/copy",
75         JMAP_URN_CORE,
76         &jmap_blob_copy,
77         JMAP_NEED_CSTATE | JMAP_READ_WRITE
78     },
79     {
80         "Core/echo",
81         JMAP_URN_CORE,
82         &jmap_core_echo,
83         0/*flags*/
84     },
85     { NULL, NULL, NULL, 0}
86 };
87 
88 jmap_method_t jmap_core_methods_nonstandard[] = {
89     {
90         "Blob/get",
91         JMAP_BLOB_EXTENSION,
92         &jmap_blob_get,
93         JMAP_NEED_CSTATE
94     },
95     {
96         "Blob/set",
97         JMAP_BLOB_EXTENSION,
98         &jmap_blob_set,
99         JMAP_NEED_CSTATE | JMAP_READ_WRITE
100     },
101     {
102         "Quota/get",
103         JMAP_QUOTA_EXTENSION,
104         &jmap_quota_get,
105         JMAP_NEED_CSTATE
106     },
107     {
108         "UserCounters/get",
109         JMAP_USERCOUNTERS_EXTENSION,
110         &jmap_usercounters_get,
111         JMAP_NEED_CSTATE
112     },
113     { NULL, NULL, NULL, 0}
114 };
115 
jmap_core_init(jmap_settings_t * settings)116 HIDDEN void jmap_core_init(jmap_settings_t *settings)
117 {
118 #define _read_opt(val, optkey) \
119     val = config_getint(optkey); \
120     if (val <= 0) { \
121         syslog(LOG_ERR, "jmap: invalid property value: %s", \
122                 imapopts[optkey].optname); \
123         val = 0; \
124     }
125     _read_opt(settings->limits[MAX_SIZE_UPLOAD],
126               IMAPOPT_JMAP_MAX_SIZE_UPLOAD);
127     settings->limits[MAX_SIZE_UPLOAD] *= 1024;
128     _read_opt(settings->limits[MAX_CONCURRENT_UPLOAD],
129               IMAPOPT_JMAP_MAX_CONCURRENT_UPLOAD);
130     _read_opt(settings->limits[MAX_SIZE_REQUEST],
131               IMAPOPT_JMAP_MAX_SIZE_REQUEST);
132     settings->limits[MAX_SIZE_REQUEST] *= 1024;
133     _read_opt(settings->limits[MAX_CONCURRENT_REQUESTS],
134               IMAPOPT_JMAP_MAX_CONCURRENT_REQUESTS);
135     _read_opt(settings->limits[MAX_CALLS_IN_REQUEST],
136               IMAPOPT_JMAP_MAX_CALLS_IN_REQUEST);
137     _read_opt(settings->limits[MAX_OBJECTS_IN_GET],
138               IMAPOPT_JMAP_MAX_OBJECTS_IN_GET);
139     _read_opt(settings->limits[MAX_OBJECTS_IN_SET],
140               IMAPOPT_JMAP_MAX_OBJECTS_IN_SET);
141     _read_opt(settings->limits[MAX_SIZE_BLOB_SET],
142               IMAPOPT_JMAP_MAX_SIZE_BLOB_SET);
143 #undef _read_opt
144 
145     json_object_set_new(settings->server_capabilities,
146             JMAP_URN_CORE,
147             json_pack("{s:i s:i s:i s:i s:i s:i s:i s:o}",
148                 "maxSizeUpload",
149                 settings->limits[MAX_SIZE_UPLOAD],
150                 "maxConcurrentUpload",
151                 settings->limits[MAX_CONCURRENT_UPLOAD],
152                 "maxSizeRequest",
153                 settings->limits[MAX_SIZE_REQUEST],
154                 "maxConcurrentRequests",
155                 settings->limits[MAX_CONCURRENT_REQUESTS],
156                 "maxCallsInRequest",
157                 settings->limits[MAX_CALLS_IN_REQUEST],
158                 "maxObjectsInGet",
159                 settings->limits[MAX_OBJECTS_IN_GET],
160                 "maxObjectsInSet",
161                 settings->limits[MAX_OBJECTS_IN_SET],
162                 "collationAlgorithms", json_array()));
163 
164     jmap_method_t *mp;
165     for (mp = jmap_core_methods_standard; mp->name; mp++) {
166         hash_insert(mp->name, mp, &settings->methods);
167     }
168 
169     if (config_getswitch(IMAPOPT_JMAP_NONSTANDARD_EXTENSIONS)) {
170         json_object_set_new(settings->server_capabilities,
171                 JMAP_QUOTA_EXTENSION, json_object());
172         json_object_set_new(settings->server_capabilities,
173                 JMAP_PERFORMANCE_EXTENSION, json_object());
174         json_object_set_new(settings->server_capabilities,
175                 JMAP_DEBUG_EXTENSION, json_object());
176         json_object_set_new(settings->server_capabilities,
177                 JMAP_BLOB_EXTENSION,
178                 json_pack("{s:i}",
179                     "maxSizeBlobSet",
180                     settings->limits[MAX_SIZE_BLOB_SET]));
181         json_object_set_new(settings->server_capabilities,
182                 JMAP_USERCOUNTERS_EXTENSION, json_object());
183 
184         for (mp = jmap_core_methods_nonstandard; mp->name; mp++) {
185             hash_insert(mp->name, mp, &settings->methods);
186         }
187     }
188 
189 }
190 
jmap_core_capabilities(json_t * account_capabilities)191 HIDDEN void jmap_core_capabilities(json_t *account_capabilities)
192 {
193     json_object_set_new(account_capabilities,
194             JMAP_URN_CORE, json_object());
195 
196     if (config_getswitch(IMAPOPT_JMAP_NONSTANDARD_EXTENSIONS)) {
197         json_object_set_new(account_capabilities,
198                 JMAP_QUOTA_EXTENSION, json_object());
199 
200         json_object_set_new(account_capabilities,
201                 JMAP_PERFORMANCE_EXTENSION, json_object());
202 
203         json_object_set_new(account_capabilities,
204                 JMAP_DEBUG_EXTENSION, json_object());
205 
206         json_object_set_new(account_capabilities,
207                 JMAP_BLOB_EXTENSION, json_object());
208 
209         json_object_set_new(account_capabilities,
210                 JMAP_USERCOUNTERS_EXTENSION, json_object());
211     }
212 }
213 
214 /*
215  * JMAP Core API Methods
216  */
217 
218 /* Core/echo method */
jmap_core_echo(jmap_req_t * req)219 static int jmap_core_echo(jmap_req_t *req)
220 {
221     json_array_append_new(req->response,
222                           json_pack("[s,O,s]", "Core/echo", req->args, req->tag));
223     return 0;
224 }
225 
jmap_copyblob(jmap_req_t * req,const char * blobid,const char * from_accountid,struct mailbox * to_mbox)226 static int jmap_copyblob(jmap_req_t *req,
227                          const char *blobid,
228                          const char *from_accountid,
229                          struct mailbox *to_mbox)
230 {
231     struct mailbox *mbox = NULL;
232     msgrecord_t *mr = NULL;
233     struct body *body = NULL;
234     const struct body *part = NULL;
235     struct buf msg_buf = BUF_INITIALIZER;
236     FILE *to_fp = NULL;
237     struct stagemsg *stage = NULL;
238 
239     int r = jmap_findblob(req, from_accountid, blobid,
240                           &mbox, &mr, &body, &part, &msg_buf);
241     if (r) return r;
242 
243     /* Create staging file */
244     time_t internaldate = time(NULL);
245     if (!(to_fp = append_newstage(to_mbox->name, internaldate, 0, &stage))) {
246         syslog(LOG_ERR, "jmap_copyblob(%s): append_newstage(%s) failed",
247                 blobid, mbox->name);
248         r = IMAP_INTERNAL;
249         goto done;
250     }
251 
252     /* Copy blob. Keep the original MIME headers, we wouldn't really
253      * know which ones are safe to rewrite for arbitrary blobs. */
254     if (part) {
255         fwrite(buf_base(&msg_buf) + part->header_offset,
256                part->header_size + part->content_size, 1, to_fp);
257     }
258     else {
259         fwrite(buf_base(&msg_buf), buf_len(&msg_buf), 1, to_fp);
260     }
261     if (ferror(to_fp)) {
262         syslog(LOG_ERR, "jmap_copyblob(%s): tofp=%s: %s",
263                blobid, append_stagefname(stage), strerror(errno));
264         r = IMAP_IOERROR;
265         goto done;
266     }
267     fclose(to_fp);
268     to_fp = NULL;
269 
270     /* Append blob to mailbox */
271     struct body *to_body = NULL;
272     struct appendstate as;
273     r = append_setup_mbox(&as, to_mbox, httpd_userid, httpd_authstate,
274             0, /*quota*/NULL, 0, 0, /*event*/0);
275     if (r) {
276         syslog(LOG_ERR, "jmap_copyblob(%s): append_setup_mbox: %s",
277                 blobid, error_message(r));
278         goto done;
279     }
280     strarray_t flags = STRARRAY_INITIALIZER;
281     strarray_append(&flags, "\\Deleted");
282     strarray_append(&flags, "\\Expunged");  // custom flag to insta-expunge!
283 	r = append_fromstage(&as, &to_body, stage, 0, internaldate, &flags, 0, NULL);
284     strarray_fini(&flags);
285 	if (r) {
286         syslog(LOG_ERR, "jmap_copyblob(%s): append_fromstage: %s",
287                 blobid, error_message(r));
288 		append_abort(&as);
289 		goto done;
290 	}
291 	message_free_body(to_body);
292 	free(to_body);
293 	r = append_commit(&as);
294 	if (r) {
295         syslog(LOG_ERR, "jmap_copyblob(%s): append_commit: %s",
296                 blobid, error_message(r));
297         goto done;
298     }
299 
300 done:
301     if (stage) append_removestage(stage);
302     if (to_fp) fclose(to_fp);
303     buf_free(&msg_buf);
304     message_free_body(body);
305     free(body);
306     msgrecord_unref(&mr);
307     jmap_closembox(req, &mbox);
308     return r;
309 }
310 
311 /* Blob/copy method */
jmap_blob_copy(jmap_req_t * req)312 static int jmap_blob_copy(jmap_req_t *req)
313 {
314     struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
315     struct jmap_copy copy;
316     json_t *val, *err = NULL;
317     size_t i = 0;
318     int r = 0;
319     struct mailbox *to_mbox = NULL;
320 
321     /* Parse request */
322     jmap_copy_parse(req, &parser, NULL, NULL, &copy, &err);
323     if (err) {
324         jmap_error(req, err);
325         goto cleanup;
326     }
327 
328     /* Check if we can upload to toAccountId */
329     r = jmap_open_upload_collection(req->accountid, &to_mbox);
330     if (r == IMAP_PERMISSION_DENIED) {
331         json_array_foreach(copy.create, i, val) {
332             json_object_set(copy.not_created, json_string_value(val),
333                     json_pack("{s:s}", "type", "toAccountNotFound"));
334         }
335         goto done;
336     } else if (r) {
337         syslog(LOG_ERR, "jmap_blob_copy: jmap_create_upload_collection(%s): %s",
338                req->accountid, error_message(r));
339         goto cleanup;
340     }
341 
342     /* Copy blobs one by one. XXX should we batch copy here? */
343     json_array_foreach(copy.create, i, val) {
344         const char *blobid = json_string_value(val);
345         r = jmap_copyblob(req, blobid, copy.from_account_id, to_mbox);
346         if (r == IMAP_NOTFOUND || r == IMAP_PERMISSION_DENIED) {
347             json_object_set_new(copy.not_created, blobid,
348                     json_pack("{s:s}", "type", "blobNotFound"));
349         }
350         else if (r) goto cleanup;
351         else json_object_set_new(copy.created, blobid, json_string(blobid));
352     }
353 
354 done:
355     /* Build response */
356     jmap_ok(req, jmap_copy_reply(&copy));
357     r = 0;
358 
359 cleanup:
360     jmap_parser_fini(&parser);
361     jmap_copy_fini(&copy);
362     mailbox_close(&to_mbox);
363     return r;
364 }
365 
366 /* Blob/get method */
367 
368 struct getblob_rec {
369     const char *blob_id;
370     uint32_t uid;
371     char *part;
372 };
373 
374 struct getblob_cb_rock {
375     jmap_req_t *req;
376     const char *blob_id;
377     hash_table *getblobs_by_mboxname;
378 };
379 
getblob_cb(const conv_guidrec_t * rec,void * vrock)380 static int getblob_cb(const conv_guidrec_t* rec, void* vrock)
381 {
382     struct getblob_cb_rock *rock = vrock;
383 
384     struct getblob_rec *getblob = xzmalloc(sizeof(struct getblob_rec));
385     getblob->blob_id = rock->blob_id;
386     getblob->uid = rec->uid;
387     getblob->part = xstrdupnull(rec->part);
388 
389     ptrarray_t *getblobs = hash_lookup(rec->mboxname, rock->getblobs_by_mboxname);
390     if (!getblobs) {
391         getblobs = ptrarray_new();
392         hash_insert(rec->mboxname, getblobs, rock->getblobs_by_mboxname);
393     }
394     ptrarray_append(getblobs, getblob);
395 
396     return 0;
397 }
398 
399 static const jmap_property_t blob_props[] = {
400     {
401         "mailboxIds",
402         NULL,
403         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
404     },
405     {
406         "threadIds",
407         NULL,
408         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
409     },
410     {
411         "emailIds",
412         NULL,
413         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
414     },
415     { NULL, NULL, 0 }
416 };
417 
jmap_blob_get(jmap_req_t * req)418 static int jmap_blob_get(jmap_req_t *req)
419 {
420     struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
421     struct jmap_get get;
422     json_t *err = NULL;
423     json_t *jval;
424     size_t i;
425 
426     /* Parse request */
427     jmap_get_parse(req, &parser, blob_props, /*allow_null_ids*/0,
428                    NULL, NULL, &get, &err);
429     if (err) {
430         jmap_error(req, err);
431         goto done;
432     }
433 
434     /* Sort blob lookups by mailbox */
435     hash_table getblobs_by_mboxname = HASH_TABLE_INITIALIZER;
436     construct_hash_table(&getblobs_by_mboxname, 128, 0);
437     json_array_foreach(get.ids, i, jval) {
438         const char *blob_id = json_string_value(jval);
439         if (*blob_id == 'G') {
440             struct getblob_cb_rock rock = { req, blob_id, &getblobs_by_mboxname };
441             int r = conversations_guid_foreach(req->cstate, blob_id + 1, getblob_cb, &rock);
442             if (r) {
443                 syslog(LOG_ERR, "jmap_blob_get: can't lookup guid %s: %s",
444                         blob_id, error_message(r));
445             }
446         }
447     }
448 
449     /* Lookup blobs by mailbox */
450     json_t *found = json_object();
451     hash_iter *iter = hash_table_iter(&getblobs_by_mboxname);
452     while (hash_iter_next(iter)) {
453         const char *mboxname = hash_iter_key(iter);
454         ptrarray_t *getblobs = hash_iter_val(iter);
455         struct mailbox *mbox = NULL;
456 
457         /* Open mailbox */
458         if (!jmap_hasrights(req, mboxname, JACL_READITEMS)) {
459             continue;
460         }
461         int r = jmap_openmbox(req, mboxname, &mbox, 0);
462         if (r) {
463             syslog(LOG_ERR, "jmap_blob_get: can't open mailbox %s: %s",
464                     mboxname, error_message(r));
465             continue;
466         }
467 
468         int j;
469         for (j = 0; j < ptrarray_size(getblobs); j++) {
470             struct getblob_rec *getblob = ptrarray_nth(getblobs, j);
471 
472             /* Read message record */
473             struct message_guid guid;
474             bit64 cid;
475             msgrecord_t *mr = NULL;
476             r = msgrecord_find(mbox, getblob->uid, &mr);
477             if (!r) r = msgrecord_get_guid(mr, &guid);
478             if (!r) r = msgrecord_get_cid(mr, &cid);
479             msgrecord_unref(&mr);
480             if (r) {
481                 syslog(LOG_ERR, "jmap_blob_get: can't read msgrecord %s:%d: %s",
482                         mboxname, getblob->uid, error_message(r));
483                 continue;
484             }
485 
486             /* Report Blob entry */
487             json_t *jblob = json_object_get(found, getblob->blob_id);
488             if (!jblob) {
489                 jblob = json_object();
490                 json_object_set_new(found, getblob->blob_id, jblob);
491             }
492             if (jmap_wantprop(get.props, "mailboxIds")) {
493                 json_t *jmailboxIds = json_object_get(jblob, "mailboxIds");
494                 if (!jmailboxIds) {
495                     jmailboxIds = json_object();
496                     json_object_set_new(jblob, "mailboxIds", jmailboxIds);
497                 }
498                 json_object_set_new(jmailboxIds, mbox->uniqueid, json_true());
499             }
500             if (jmap_wantprop(get.props, "emailIds")) {
501                 json_t *jemailIds = json_object_get(jblob, "emailIds");
502                 if (!jemailIds) {
503                     jemailIds = json_object();
504                     json_object_set_new(jblob, "emailIds", jemailIds);
505                 }
506                 char emailid[JMAP_EMAILID_SIZE];
507                 jmap_set_emailid(&guid, emailid);
508                 json_object_set_new(jemailIds, emailid, json_true());
509             }
510             if (jmap_wantprop(get.props, "threadIds")) {
511                 json_t *jthreadIds = json_object_get(jblob, "threadIds");
512                 if (!jthreadIds) {
513                     jthreadIds = json_object();
514                     json_object_set_new(jblob, "threadIds", jthreadIds);
515                 }
516                 char threadid[JMAP_THREADID_SIZE];
517                 jmap_set_threadid(cid, threadid);
518                 json_object_set_new(jthreadIds, threadid, json_true());
519             }
520         }
521 
522        jmap_closembox(req, &mbox);
523     }
524 
525     /* Clean up memory */
526     hash_iter_reset(iter);
527     while (hash_iter_next(iter)) {
528         ptrarray_t *getblobs = hash_iter_val(iter);
529         struct getblob_rec *getblob;
530         while ((getblob = ptrarray_pop(getblobs))) {
531             free(getblob->part);
532             free(getblob);
533         }
534         ptrarray_free(getblobs);
535     }
536     hash_iter_free(&iter);
537     free_hash_table(&getblobs_by_mboxname, NULL);
538 
539     /* Report found blobs */
540     if (json_object_size(found)) {
541         const char *blob_id;
542         json_t *jblob;
543         json_object_foreach(found, blob_id, jblob) {
544             json_array_append(get.list, jblob);
545         }
546     }
547 
548     /* Report unknown or erroneous blobs */
549     json_array_foreach(get.ids, i, jval) {
550         const char *blob_id = json_string_value(jval);
551         if (!json_object_get(found, blob_id)) {
552             json_array_append_new(get.not_found, json_string(blob_id));
553         }
554     }
555 
556     json_decref(found);
557 
558     /* Reply */
559     jmap_ok(req, jmap_get_reply(&get));
560 
561 done:
562     jmap_parser_fini(&parser);
563     jmap_get_fini(&get);
564     return 0;
565 }
566 
567 static const jmap_property_t blob_set_props[] = {
568     {
569         "id",
570         NULL,
571         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE | JMAP_PROP_ALWAYS_GET
572     },
573     {
574         "content",
575         NULL,
576         0
577     },
578     {
579         "content64",
580         NULL,
581         0
582     },
583     {
584         "type",
585         NULL,
586         0
587     },
588 
589     { NULL, NULL, 0 }
590 };
591 
592 
jmap_blob_set(struct jmap_req * req)593 static int jmap_blob_set(struct jmap_req *req)
594 {
595     struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
596     struct jmap_set set;
597     json_t *jerr = NULL;
598     int r = 0;
599     time_t now = time(NULL);
600 
601     /* Parse arguments */
602     jmap_set_parse(req, &parser, blob_set_props, NULL, NULL, &set, &jerr);
603     if (jerr) {
604         jmap_error(req, jerr);
605         goto done;
606     }
607 
608     /* create */
609     const char *key;
610     json_t *arg;
611     json_object_foreach(set.create, key, arg) {
612         struct buf *buf = buf_new();
613         struct message_guid guidobj;
614         char datestr[RFC3339_DATETIME_MAX];
615         char blob_id[JMAP_BLOBID_SIZE];
616 
617         json_t *jitem = json_object_get(arg, "content");
618         if (JNOTNULL(jitem) && json_is_string(jitem)) {
619             buf_init_ro(buf, json_string_value(jitem), json_string_length(jitem));
620         }
621         else {
622             json_t *jitem64 = json_object_get(arg, "content64");
623             if (JNOTNULL(jitem64) && json_is_string(jitem64)) {
624                 int r = charset_decode(buf, json_string_value(jitem64),
625                                        json_string_length(jitem64), ENCODING_BASE64);
626                 if (r) buf_free(buf);
627             }
628         }
629 
630         if (!buf_base(buf)) {
631             jerr = json_pack("{s:s}", "type", "invalidProperties");
632             json_object_set_new(set.not_updated, key, jerr);
633             buf_destroy(buf);
634             continue;
635         }
636 
637         json_t *jtype = json_object_get(arg, "type");
638         const char *type = json_string_value(jtype);
639         if (!type) type = "application/octet-stream";
640 
641         message_guid_generate(&guidobj, buf_base(buf), buf_len(buf));
642         jmap_set_blobid(&guidobj, blob_id);
643         time_to_rfc3339(now, datestr, RFC3339_DATETIME_MAX);
644 
645         // json_string_value into the request lasts the lifetime of the request, so it's
646         // safe to zerocopy these blobs!
647         hash_insert(blob_id, buf, req->inmemory_blobs);
648 
649         json_object_set_new(set.created, key, json_pack("{s:s, s:s, s:i, s:s, s:s}",
650             "id", blob_id,
651             "blobId", blob_id,
652             "size", buf_len(buf),
653             "expires", datestr,
654             "type", type));
655 
656         jmap_add_id(req, key, blob_id);
657     }
658 
659     const char *uid;
660     json_object_foreach(set.update, uid, arg) {
661         jerr = json_pack("{s:s}", "type", "notFound");
662         json_object_set_new(set.not_updated, key, jerr);
663     }
664 
665     size_t index;
666     json_t *juid;
667     json_array_foreach(set.destroy, index, juid) {
668         jerr = json_pack("{s:s}", "type", "notFound");
669         json_object_set_new(set.not_destroyed, json_string_value(juid), jerr);
670     }
671 
672     set.old_state = set.new_state = 0;
673     jmap_ok(req, jmap_set_reply(&set));
674 
675 done:
676     jmap_parser_fini(&parser);
677     jmap_set_fini(&set);
678     return r;
679 }
680 
681 
682 /* Quota/get method */
683 static const jmap_property_t quota_props[] = {
684     {
685         "id",
686         NULL,
687         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
688     },
689     {
690         "used",
691         NULL,
692         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
693     },
694     {
695         "total",
696         NULL,
697         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
698     },
699     { NULL, NULL, 0 }
700 };
701 
jmap_quota_get(jmap_req_t * req)702 static int jmap_quota_get(jmap_req_t *req)
703 {
704     struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
705     struct jmap_get get;
706     json_t *err = NULL;
707     char *inboxname = mboxname_user_mbox(req->accountid, NULL);
708 
709     /* Parse request */
710     jmap_get_parse(req, &parser, quota_props, /*allow_null_ids*/1,
711                    NULL, NULL, &get, &err);
712     if (err) {
713         jmap_error(req, err);
714         goto done;
715     }
716 
717     int want_mail_quota = !get.ids || json_is_null(get.ids);
718     size_t i;
719     json_t *jval;
720     json_array_foreach(get.ids, i, jval) {
721         if (strcmp("mail", json_string_value(jval))) {
722             json_array_append(get.not_found, jval);
723         }
724         else want_mail_quota = 1;
725     }
726 
727     if (want_mail_quota) {
728         struct quota quota;
729         quota_init(&quota, inboxname);
730         int r = quota_read_withconversations(&quota);
731         if (!r) {
732             quota_t total = quota.limits[QUOTA_STORAGE] * quota_units[QUOTA_STORAGE];
733             quota_t used = quota.useds[QUOTA_STORAGE];
734             json_t *jquota = json_object();
735             json_object_set_new(jquota, "id", json_string("mail"));
736             json_object_set_new(jquota, "used", json_integer(used));
737             json_object_set_new(jquota, "total", json_integer(total));
738             json_array_append_new(get.list, jquota);
739         }
740         else {
741             syslog(LOG_ERR, "jmap_quota_get: can't read quota for %s: %s",
742                     inboxname, error_message(r));
743             json_array_append_new(get.not_found, json_string("mail"));
744         }
745         quota_free(&quota);
746     }
747 
748 
749     modseq_t quotamodseq = mboxname_readquotamodseq(inboxname);
750     struct buf buf = BUF_INITIALIZER;
751     buf_printf(&buf, MODSEQ_FMT, quotamodseq);
752     get.state = buf_release(&buf);
753 
754     jmap_ok(req, jmap_get_reply(&get));
755 
756 done:
757     jmap_parser_fini(&parser);
758     jmap_get_fini(&get);
759     free(inboxname);
760     return 0;
761 }
762 
763 /* UserCounters/get method */
764 static const jmap_property_t usercounters_props[] = {
765     {
766         "id",
767         NULL,
768         JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE | JMAP_PROP_ALWAYS_GET
769     },
770     {
771         "highestModSeq",
772         NULL,
773         JMAP_PROP_SERVER_SET
774     },
775     {
776         "mailModSeq",
777         NULL,
778         JMAP_PROP_SERVER_SET
779     },
780     {
781         "calendarModSeq",
782         NULL,
783         JMAP_PROP_SERVER_SET
784     },
785     {
786         "contactsModSeq",
787         NULL,
788         JMAP_PROP_SERVER_SET
789     },
790     {
791         "notesModSeq",
792         NULL,
793         JMAP_PROP_SERVER_SET
794     },
795     {
796         "submissionModSeq",
797         NULL,
798         JMAP_PROP_SERVER_SET
799     },
800     {
801         "mailDeletedModSeq",
802         NULL,
803         JMAP_PROP_SERVER_SET
804     },
805     {
806         "calendarDeletedModSeq",
807         NULL,
808         JMAP_PROP_SERVER_SET
809     },
810     {
811         "contactsDeletedModSeq",
812         NULL,
813         JMAP_PROP_SERVER_SET
814     },
815     {
816         "notesDeletedModSeq",
817         NULL,
818         JMAP_PROP_SERVER_SET
819     },
820     {
821         "submissionDeletedModSeq",
822         NULL,
823         JMAP_PROP_SERVER_SET
824     },
825     {
826         "mailFoldersModSeq",
827         NULL,
828         JMAP_PROP_SERVER_SET
829     },
830     {
831         "calendarFoldersModSeq",
832         NULL,
833         JMAP_PROP_SERVER_SET
834     },
835     {
836         "contactFoldersModSeq",
837         NULL,
838         JMAP_PROP_SERVER_SET
839     },
840     {
841         "notesFoldersModSeq",
842         NULL,
843         JMAP_PROP_SERVER_SET
844     },
845     {
846         "submissionFoldersModSeq",
847         NULL,
848         JMAP_PROP_SERVER_SET
849     },
850     {
851         "mailFoldersDeletedModSeq",
852         NULL,
853         JMAP_PROP_SERVER_SET
854     },
855     {
856         "calendarFoldersDeletedModSeq",
857         NULL,
858         JMAP_PROP_SERVER_SET
859     },
860     {
861         "contactFoldersDeletedModSeq",
862         NULL,
863         JMAP_PROP_SERVER_SET
864     },
865     {
866         "notesFoldersDeletedModSeq",
867         NULL,
868         JMAP_PROP_SERVER_SET
869     },
870     {
871         "quotaModSeq",
872         NULL,
873         JMAP_PROP_SERVER_SET
874     },
875     {
876         "raclModSeq",
877         NULL,
878         JMAP_PROP_SERVER_SET
879     },
880     {
881         "uidValidity",
882         NULL,
883         JMAP_PROP_SERVER_SET
884     },
885 
886     { NULL, NULL, 0 }
887 };
888 
usercounters_get(jmap_req_t * req,struct jmap_get * get)889 static void usercounters_get(jmap_req_t *req, struct jmap_get *get)
890 {
891     /* Read script */
892     json_t *res = json_pack("{s:s}", "id", "singleton");
893 
894     if (jmap_wantprop(get->props, "highestModSeq"))
895         json_object_set_new(res, "highestModSeq",
896                             json_integer(req->counters.highestmodseq));
897 
898     if (jmap_wantprop(get->props, "mailModSeq"))
899         json_object_set_new(res, "mailModSeq",
900                             json_integer(req->counters.mailmodseq));
901     if (jmap_wantprop(get->props, "calendarModSeq"))
902         json_object_set_new(res, "calendarModSeq",
903                             json_integer(req->counters.caldavmodseq));
904     if (jmap_wantprop(get->props, "contactsModSeq"))
905         json_object_set_new(res, "contactsModSeq",
906                             json_integer(req->counters.carddavmodseq));
907     if (jmap_wantprop(get->props, "notesModSeq"))
908         json_object_set_new(res, "notesModSeq",
909                             json_integer(req->counters.notesmodseq));
910 
911     if (jmap_wantprop(get->props, "mailDeletedModSeq"))
912         json_object_set_new(res, "mailDeletedModSeq",
913                             json_integer(req->counters.maildeletedmodseq));
914     if (jmap_wantprop(get->props, "calendarDeletedModSeq"))
915         json_object_set_new(res, "calendarDeletedModSeq",
916                             json_integer(req->counters.caldavdeletedmodseq));
917     if (jmap_wantprop(get->props, "contactsDeletedModSeq"))
918         json_object_set_new(res, "contactsDeletedModSeq",
919                             json_integer(req->counters.carddavdeletedmodseq));
920     if (jmap_wantprop(get->props, "notesDeletedModSeq"))
921         json_object_set_new(res, "notesDeletedModSeq",
922                             json_integer(req->counters.notesdeletedmodseq));
923 
924     if (jmap_wantprop(get->props, "mailFoldersModSeq"))
925         json_object_set_new(res, "mailFoldersModSeq",
926                             json_integer(req->counters.mailfoldersmodseq));
927     if (jmap_wantprop(get->props, "calendarFoldersModSeq"))
928         json_object_set_new(res, "calendarFoldersModSeq",
929                             json_integer(req->counters.caldavfoldersmodseq));
930     if (jmap_wantprop(get->props, "contactsFoldersModSeq"))
931         json_object_set_new(res, "contactsFoldersModSeq",
932                             json_integer(req->counters.carddavfoldersmodseq));
933     if (jmap_wantprop(get->props, "notesFoldersModSeq"))
934         json_object_set_new(res, "notesFoldersModSeq",
935                             json_integer(req->counters.notesfoldersmodseq));
936 
937     if (jmap_wantprop(get->props, "mailFoldersDeletedModSeq"))
938         json_object_set_new(res, "mailFoldersDeletedModSeq",
939                             json_integer(req->counters.mailfoldersdeletedmodseq));
940     if (jmap_wantprop(get->props, "calendarFoldersDeletedModSeq"))
941         json_object_set_new(res, "calendarFoldersDeletedModSeq",
942                             json_integer(req->counters.caldavfoldersdeletedmodseq));
943     if (jmap_wantprop(get->props, "contactsFoldersDeletedModSeq"))
944         json_object_set_new(res, "contactsFoldersDeletedModSeq",
945                             json_integer(req->counters.carddavfoldersdeletedmodseq));
946     if (jmap_wantprop(get->props, "notesFoldersDeletedModSeq"))
947         json_object_set_new(res, "notesFoldersDeletedModSeq",
948                             json_integer(req->counters.notesfoldersdeletedmodseq));
949 
950     if (jmap_wantprop(get->props, "quotaModSeq"))
951         json_object_set_new(res, "quotaModSeq",
952                             json_integer(req->counters.quotamodseq));
953     if (jmap_wantprop(get->props, "raclModSeq"))
954         json_object_set_new(res, "raclModSeq",
955                             json_integer(req->counters.raclmodseq));
956 
957     if (jmap_wantprop(get->props, "uidValidity"))
958         json_object_set_new(res, "uidValidity",
959                             json_integer(req->counters.uidvalidity));
960 
961     json_array_append_new(get->list, res);
962 }
963 
jmap_usercounters_get(jmap_req_t * req)964 static int jmap_usercounters_get(jmap_req_t *req)
965 {
966     struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
967     struct jmap_get get;
968     json_t *err = NULL;
969 
970     /* Parse request */
971     jmap_get_parse(req, &parser, usercounters_props, /*allow_null_ids*/1,
972                    NULL, NULL, &get, &err);
973     if (err) {
974         jmap_error(req, err);
975         goto done;
976     }
977 
978     /* Does the client request specific responses? */
979     if (JNOTNULL(get.ids)) {
980         json_t *jval;
981         size_t i;
982 
983         json_array_foreach(get.ids, i, jval) {
984             const char *id = json_string_value(jval);
985 
986             if (!strcmp(id, "singleton"))
987                 usercounters_get(req, &get);
988             else
989                 json_array_append(get.not_found, jval);
990         }
991     }
992     else usercounters_get(req, &get);
993 
994     /* Build response */
995     struct buf buf = BUF_INITIALIZER;
996     buf_printf(&buf, MODSEQ_FMT, req->counters.highestmodseq);
997     get.state = buf_release(&buf);
998     jmap_ok(req, jmap_get_reply(&get));
999 
1000 done:
1001     jmap_parser_fini(&parser);
1002     jmap_get_fini(&get);
1003 
1004     return 0;
1005 }
1006