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, ©, &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(©));
357 r = 0;
358
359 cleanup:
360 jmap_parser_fini(&parser);
361 jmap_copy_fini(©);
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("a, inboxname);
730 int r = quota_read_withconversations("a);
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("a);
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