Coverage Report

Created: 2026-08-31 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-storage/index/imapc/imapc-save.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "istream.h"
6
#include "istream-crlf.h"
7
#include "ostream.h"
8
#include "imap-date.h"
9
#include "imap-util.h"
10
#include "imap-seqset.h"
11
#include "imap-quote.h"
12
#include "index-mail.h"
13
#include "mail-copy.h"
14
#include "mailbox-list-private.h"
15
#include "imapc-msgmap.h"
16
#include "imapc-storage.h"
17
#include "imapc-sync.h"
18
#include "imapc-mail.h"
19
#include "seq-set-builder.h"
20
21
struct imapc_save_context {
22
  struct mail_save_context ctx;
23
24
  struct imapc_mailbox *mbox;
25
  struct imapc_mailbox *src_mbox;
26
  struct mail_index_transaction *trans;
27
28
  int fd;
29
  char *temp_path;
30
  struct istream *input;
31
32
  uint32_t dest_uid_validity;
33
  ARRAY_TYPE(seq_range) dest_saved_uids;
34
  unsigned int save_count;
35
36
  bool failed:1;
37
  bool finished:1;
38
};
39
40
struct imapc_save_cmd_context {
41
  struct imapc_save_context *ctx;
42
  int ret;
43
};
44
45
0
#define IMAPC_SAVECTX(s)  container_of(s, struct imapc_save_context, ctx)
46
0
#define IMAPC_SERVER_CMDLINE_MAX_LEN  8000
47
48
void imapc_transaction_save_rollback(struct mail_save_context *_ctx);
49
static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox);
50
51
struct mail_save_context *
52
imapc_save_alloc(struct mailbox_transaction_context *t)
53
0
{
54
0
  struct imapc_mailbox *mbox = IMAPC_MAILBOX(t->box);
55
0
  struct imapc_save_context *ctx;
56
57
0
  i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
58
59
0
  if (t->save_ctx == NULL) {
60
0
    ctx = i_new(struct imapc_save_context, 1);
61
0
    ctx->ctx.transaction = t;
62
0
    ctx->mbox = mbox;
63
0
    ctx->src_mbox = NULL;
64
0
    ctx->trans = t->itrans;
65
0
    ctx->fd = -1;
66
0
    t->save_ctx = &ctx->ctx;
67
0
  }
68
0
  return t->save_ctx;
69
0
}
70
71
int imapc_save_begin(struct mail_save_context *_ctx, struct istream *input)
72
0
{
73
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
74
0
  const char *path;
75
76
0
  i_assert(ctx->fd == -1);
77
78
0
  if (imapc_storage_client_handle_auth_failure(ctx->mbox->storage->client))
79
0
    return -1;
80
81
0
  ctx->fd = imapc_client_create_temp_fd(ctx->mbox->storage->client->client,
82
0
                &path);
83
0
  if (ctx->fd == -1) {
84
0
    mail_set_critical(_ctx->dest_mail,
85
0
          "Couldn't create temp file %s", path);
86
0
    ctx->failed = TRUE;
87
0
    return -1;
88
0
  }
89
  /* we may not know the size of the input, or be sure that it contains
90
     only CRLFs. so we'll always first write the mail to a temp file and
91
     upload it from there to remote server. */
92
0
  ctx->finished = FALSE;
93
0
  ctx->temp_path = i_strdup(path);
94
0
  ctx->input = i_stream_create_crlf(input);
95
0
  _ctx->data.output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
96
0
  o_stream_cork(_ctx->data.output);
97
0
  return 0;
98
0
}
99
100
int imapc_save_continue(struct mail_save_context *_ctx)
101
0
{
102
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
103
104
0
  if (ctx->failed)
105
0
    return -1;
106
107
0
  if (index_storage_save_continue(_ctx, ctx->input, NULL) < 0) {
108
0
    ctx->failed = TRUE;
109
0
    return -1;
110
0
  }
111
0
  return 0;
112
0
}
113
114
static void imapc_save_appenduid(struct imapc_save_context *ctx,
115
         const struct imapc_command_reply *reply,
116
         uint32_t *uid_r)
117
0
{
118
0
  const char *const *args;
119
0
  uint32_t uid_validity, dest_uid;
120
121
0
  *uid_r = 0;
122
123
  /* <uidvalidity> <dest uid-set> */
124
0
  args = t_strsplit(reply->resp_text_value, " ");
125
0
  if (str_array_length(args) != 2)
126
0
    return;
127
128
0
  if (str_to_uint32(args[0], &uid_validity) < 0)
129
0
    return;
130
0
  if (ctx->dest_uid_validity == 0)
131
0
    ctx->dest_uid_validity = uid_validity;
132
0
  else if (ctx->dest_uid_validity != uid_validity)
133
0
    return;
134
135
0
  if (str_to_uint32(args[1], &dest_uid) == 0) {
136
0
    seq_range_array_add_with_init(&ctx->dest_saved_uids,
137
0
                32, dest_uid);
138
0
    *uid_r = dest_uid;
139
0
  }
140
0
}
141
142
static void
143
imapc_save_add_to_index(struct imapc_save_context *ctx, uint32_t uid)
144
0
{
145
0
  struct mail *_mail = ctx->ctx.dest_mail;
146
0
  struct index_mail *imail = INDEX_MAIL(_mail);
147
0
  uint32_t seq;
148
149
  /* we'll temporarily append messages and at commit time expunge
150
     them all, since we can't guarantee that no one else has saved
151
     messages to remote server during our transaction */
152
0
  mail_index_append(ctx->trans, uid, &seq);
153
0
  mail_set_seq_saving(_mail, seq);
154
0
  imail->data.no_caching = TRUE;
155
0
  imail->data.forced_no_caching = TRUE;
156
157
0
  if (ctx->fd != -1) {
158
0
    struct imapc_mail *imapc_mail = IMAPC_MAIL(_mail);
159
0
    imail->data.stream = i_stream_create_fd_autoclose(&ctx->fd, 0);
160
0
    imapc_mail->header_fetched = TRUE;
161
0
    imapc_mail->body_fetched = TRUE;
162
    /* The saved stream wasn't actually read, but it needs to be
163
       set accessed to avoid assert-crash. */
164
0
    _mail->mail_stream_accessed = TRUE;
165
0
    imapc_mail_init_stream(imapc_mail);
166
0
  }
167
168
0
  ctx->save_count++;
169
0
}
170
171
static void imapc_save_callback(const struct imapc_command_reply *reply,
172
        void *context)
173
0
{
174
0
  struct imapc_save_cmd_context *ctx = context;
175
0
  uint32_t uid = 0;
176
177
0
  if (reply->state == IMAPC_COMMAND_STATE_OK) {
178
0
    if (reply->resp_text_key != NULL &&
179
0
        strcasecmp(reply->resp_text_key, "APPENDUID") == 0)
180
0
      imapc_save_appenduid(ctx->ctx, reply, &uid);
181
0
    imapc_save_add_to_index(ctx->ctx, uid);
182
0
    ctx->ret = 0;
183
0
  } else if (imapc_storage_client_handle_auth_failure(ctx->ctx->mbox->storage->client)) {
184
0
    ctx->ret = -1;
185
0
  } else if (reply->state == IMAPC_COMMAND_STATE_NO) {
186
0
    imapc_copy_error_from_reply(ctx->ctx->mbox->storage,
187
0
              MAIL_ERROR_PARAMS, reply);
188
0
    ctx->ret = -1;
189
0
  } else {
190
0
    mailbox_set_critical(&ctx->ctx->mbox->box,
191
0
      "imapc: APPEND failed: %s", reply->text_full);
192
0
    ctx->ret = -1;
193
0
  }
194
0
  imapc_client_stop(ctx->ctx->mbox->storage->client->client);
195
0
}
196
197
static void
198
imapc_save_noop_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
199
       void *context)
200
0
{
201
0
  struct imapc_save_cmd_context *ctx = context;
202
203
  /* we don't really care about the reply */
204
0
  ctx->ret = 0;
205
0
  imapc_client_stop(ctx->ctx->mbox->storage->client->client);
206
0
}
207
208
static void
209
imapc_copy_rollback_store_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
210
           void *context)
211
0
{
212
0
  struct imapc_save_context *ctx = context;
213
  /* Can't do much about a non successful STORE here */
214
0
  if (reply->state != IMAPC_COMMAND_STATE_OK) {
215
0
    e_error(ctx->src_mbox->box.event,
216
0
      "imapc: Failed to set \\Deleted flag for rolling back "
217
0
      "failed copy: %s", reply->text_full);
218
0
    ctx->src_mbox->rollback_pending = FALSE;
219
0
    ctx->finished = TRUE;
220
0
    ctx->failed = TRUE;
221
0
  } else {
222
0
    i_assert(ctx->src_mbox->rollback_pending);
223
0
  }
224
  /* No need stop the imapc client here there is always an additional
225
     expunge callback after this. */
226
0
}
227
228
static void
229
imapc_copy_rollback_expunge_callback(const struct imapc_command_reply *reply ATTR_UNUSED,
230
             void *context)
231
0
{
232
0
  struct imapc_save_context *ctx = context;
233
234
  /* Can't do much about a non successful EXPUNGE here */
235
0
  if (reply->state != IMAPC_COMMAND_STATE_OK) {
236
0
    e_error(ctx->src_mbox->box.event,
237
0
      "imapc: Failed to expunge messages for rolling back "
238
0
      "failed copy: %s", reply->text_full);
239
0
    ctx->src_mbox->rollback_pending = FALSE;
240
0
    ctx->finished = TRUE;
241
0
    ctx->failed = TRUE;
242
0
  } else {
243
0
    ctx->finished = TRUE;
244
0
    ctx->src_mbox->rollback_pending = FALSE;
245
0
  }
246
0
  imapc_client_stop(ctx->src_mbox->storage->client->client);
247
0
}
248
249
static void
250
imapc_append_keywords(string_t *str, struct mail_keywords *kw)
251
0
{
252
0
  const ARRAY_TYPE(keywords) *kw_arr;
253
0
  const char *kw_str;
254
0
  unsigned int i;
255
256
0
  kw_arr = mail_index_get_keywords(kw->index);
257
0
  for (i = 0; i < kw->count; i++) {
258
0
    kw_str = array_idx_elem(kw_arr, kw->idx[i]);
259
0
    if (str_len(str) > 1)
260
0
      str_append_c(str, ' ');
261
0
    str_append(str, kw_str);
262
0
  }
263
0
}
264
265
static int imapc_save_append(struct imapc_save_context *ctx)
266
0
{
267
0
  struct mail_save_context *_ctx = &ctx->ctx;
268
0
  struct mail_save_data *mdata = &_ctx->data;
269
0
  struct imapc_command *cmd;
270
0
  struct imapc_save_cmd_context sctx;
271
0
  struct istream *input;
272
0
  const char *flags = "", *internaldate = "";
273
274
0
  if (mdata->flags != 0 || mdata->keywords != NULL) {
275
0
    string_t *str = t_str_new(64);
276
277
0
    str_append(str, " (");
278
0
    imap_write_flags(str, mdata->flags & ENUM_NEGATE(MAIL_RECENT),
279
0
         NULL);
280
0
    if (mdata->keywords != NULL)
281
0
      imapc_append_keywords(str, mdata->keywords);
282
0
    str_append_c(str, ')');
283
0
    flags = str_c(str);
284
0
  }
285
0
  if (mdata->received_date != (time_t)-1) {
286
0
    internaldate = t_strdup_printf(" \"%s\"",
287
0
      imap_to_datetime(mdata->received_date));
288
0
  }
289
290
0
  ctx->mbox->exists_received = FALSE;
291
292
0
  input = i_stream_create_fd(ctx->fd, IO_BLOCK_SIZE);
293
0
  sctx.ctx = ctx;
294
0
  sctx.ret = -2;
295
0
  cmd = imapc_client_cmd(ctx->mbox->storage->client->client,
296
0
             imapc_save_callback, &sctx);
297
0
  imapc_command_sendf(cmd, "APPEND %s%1s%1s %p",
298
0
    imapc_mailbox_get_remote_name(ctx->mbox),
299
0
    flags, internaldate, input);
300
0
  i_stream_unref(&input);
301
0
  while (sctx.ret == -2)
302
0
    imapc_mailbox_run(ctx->mbox);
303
304
0
  if (sctx.ret == 0 && ctx->mbox->selected &&
305
0
      !ctx->mbox->exists_received) {
306
    /* e.g. Courier doesn't send EXISTS reply before the tagged
307
       APPEND reply. That isn't exactly required by the IMAP RFC,
308
       but it makes the behavior better. See if NOOP finds
309
       the mail. */
310
0
    sctx.ret = -2;
311
0
    cmd = imapc_client_cmd(ctx->mbox->storage->client->client,
312
0
               imapc_save_noop_callback, &sctx);
313
0
    imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE);
314
0
    imapc_command_send(cmd, "NOOP");
315
0
    while (sctx.ret == -2)
316
0
      imapc_mailbox_run(ctx->mbox);
317
0
  }
318
0
  return sctx.ret;
319
0
}
320
321
int imapc_save_finish(struct mail_save_context *_ctx)
322
0
{
323
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
324
0
  struct mail_storage *storage = _ctx->transaction->box->storage;
325
326
0
  ctx->finished = TRUE;
327
328
0
  if (!ctx->failed) {
329
0
    if (o_stream_finish(_ctx->data.output) < 0) {
330
0
      if (!mail_storage_set_error_from_errno(storage)) {
331
0
        mail_set_critical(_ctx->dest_mail,
332
0
          "write(%s) failed: %s", ctx->temp_path,
333
0
          o_stream_get_error(_ctx->data.output));
334
0
      }
335
0
      ctx->failed = TRUE;
336
0
    }
337
0
  }
338
339
0
  if (!ctx->failed) {
340
0
    if (imapc_save_append(ctx) < 0)
341
0
      ctx->failed = TRUE;
342
0
  }
343
344
0
  o_stream_unref(&_ctx->data.output);
345
0
  i_stream_unref(&ctx->input);
346
0
  i_close_fd_path(&ctx->fd, ctx->temp_path);
347
0
  i_free(ctx->temp_path);
348
0
  index_save_context_free(_ctx);
349
0
  return ctx->failed ? -1 : 0;
350
0
}
351
352
void imapc_save_cancel(struct mail_save_context *_ctx)
353
0
{
354
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
355
356
0
  ctx->failed = TRUE;
357
0
  (void)imapc_transaction_save_commit_pre(_ctx);
358
0
  (void)imapc_save_finish(_ctx);
359
0
}
360
361
static void imapc_copy_bulk_finish(struct imapc_save_context *ctx)
362
0
{
363
0
  while (ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL)
364
0
    imapc_mailbox_run_nofetch(ctx->src_mbox);
365
0
}
366
367
int imapc_transaction_save_commit_pre(struct mail_save_context *_ctx)
368
0
{
369
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
370
0
  struct mail_transaction_commit_changes *changes =
371
0
    _ctx->transaction->changes;
372
0
  uint32_t i, last_seq;
373
374
0
  i_assert(ctx->finished || ctx->failed);
375
376
  /* expunge all added messages from index before commit */
377
0
  last_seq = mail_index_view_get_messages_count(_ctx->transaction->view);
378
0
  if (last_seq == 0)
379
0
    return -1;
380
0
  for (i = 0; i < ctx->save_count; i++)
381
0
    mail_index_expunge(ctx->trans, last_seq - i);
382
383
0
  if (!ctx->failed && array_is_created(&ctx->dest_saved_uids)) {
384
0
    changes->uid_validity = ctx->dest_uid_validity;
385
0
    array_append_array(&changes->saved_uids, &ctx->dest_saved_uids);
386
0
  }
387
0
  return 0;
388
0
}
389
390
int imapc_transaction_save_commit(struct mailbox_transaction_context *t)
391
0
{
392
0
       struct imapc_save_context *ctx = NULL;
393
0
       struct imapc_mailbox *src_mbox = NULL;
394
395
0
       if (t->save_ctx != NULL) {
396
0
               ctx = IMAPC_SAVECTX(t->save_ctx);
397
0
               src_mbox = ctx->src_mbox;
398
0
       }
399
400
0
       if (src_mbox != NULL && src_mbox->pending_copy_request != NULL) {
401
         /* If there is still a copy command to send flush it now */
402
0
         imapc_mail_copy_bulk_flush(src_mbox);
403
0
         imapc_copy_bulk_finish(ctx);
404
0
       }
405
406
0
       if (ctx != NULL)
407
0
         return ctx->failed ? -1 : 0;
408
0
       return 0;
409
0
}
410
411
void imapc_transaction_save_commit_post(struct mail_save_context *_ctx,
412
          struct mail_index_transaction_commit_result *result ATTR_UNUSED)
413
0
{
414
0
  imapc_transaction_save_rollback(_ctx);
415
0
}
416
417
static void
418
imapc_expunge_construct_cmd_str(string_t *store_cmd,
419
        string_t *expunge_cmd,
420
        string_t *uids)
421
0
{
422
0
  str_append(store_cmd, "UID STORE ");
423
0
  str_append_str(store_cmd, uids);
424
0
  str_append(store_cmd, " +FLAGS (\\Deleted)");
425
0
  str_append(expunge_cmd, "UID EXPUNGE ");
426
0
  str_append_str(expunge_cmd, uids);
427
  /* Clear already appended uids */
428
0
  str_truncate(uids, 0);
429
0
}
430
431
static void
432
imapc_expunge_send_cmd_str(struct imapc_save_context *ctx,
433
         string_t *uids)
434
0
{
435
0
  struct imapc_command *store_cmd, *expunge_cmd;
436
437
0
  string_t *store_cmd_str, *expunge_cmd_str;
438
0
  store_cmd_str = t_str_new(128);
439
0
  expunge_cmd_str = t_str_new(128);
440
441
0
  imapc_expunge_construct_cmd_str(store_cmd_str, expunge_cmd_str, uids);
442
  /* Make sure line length is less than 8k */
443
0
  i_assert(str_len(store_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN);
444
0
  i_assert(str_len(expunge_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN);
445
446
0
  store_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box,
447
0
               imapc_copy_rollback_store_callback,
448
0
               ctx);
449
0
  expunge_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box,
450
0
                 imapc_copy_rollback_expunge_callback,
451
0
                 ctx);
452
0
  ctx->src_mbox->rollback_pending = TRUE;
453
0
  imapc_command_send(store_cmd, str_c(store_cmd_str));
454
0
  imapc_command_send(expunge_cmd, str_c(expunge_cmd_str));
455
0
}
456
457
static void
458
imapc_rollback_send_expunge(struct imapc_save_context *ctx)
459
0
{
460
0
  string_t *uids_str;
461
0
  struct seqset_builder *seqset_builder;
462
0
  struct seq_range_iter iter;
463
0
  unsigned int i = 0;
464
0
  uint32_t uid;
465
466
0
  if (!array_not_empty(&ctx->src_mbox->copy_rollback_expunge_uids))
467
0
    return;
468
469
0
  uids_str = t_str_new(128);
470
0
  seqset_builder = seqset_builder_init(uids_str);
471
0
  seq_range_array_iter_init(&iter, &ctx->src_mbox->copy_rollback_expunge_uids);
472
473
  /* Iterate over all uids that must be rolled back */
474
0
  while (seq_range_array_iter_nth(&iter, i++, &uid)) {
475
    /* Try to add the to the seqset builder while respecting
476
       the maximum length of IMAPC_SERVER_CMDLINE_MAX_LEN. */
477
0
    if (!seqset_builder_try_add(seqset_builder,
478
0
              IMAPC_SERVER_CMDLINE_MAX_LEN -
479
0
              strlen("UID STORE  +FLAGS (\\Deleted)"),
480
0
              uid)) {
481
      /* Maximum length is reached send the rollback
482
         and wait for it to be finished. */
483
0
      seqset_builder_deinit(&seqset_builder);
484
0
      imapc_expunge_send_cmd_str(ctx, uids_str);
485
0
      while (ctx->src_mbox->rollback_pending)
486
0
        imapc_mailbox_run_nofetch(ctx->src_mbox);
487
488
      /* Truncate the uids_str and create a new
489
         seqset_builder for the next command */
490
0
      str_truncate(uids_str, 0);
491
0
      seqset_builder = seqset_builder_init(uids_str);
492
      /* Make sure the current uid which is part of
493
         the next uid_str */
494
0
      seqset_builder_add(seqset_builder, uid);
495
0
    }
496
0
  }
497
0
  seqset_builder_deinit(&seqset_builder);
498
0
  if (str_len(uids_str) > 0)
499
0
    imapc_expunge_send_cmd_str(ctx, uids_str);
500
0
  while (ctx->src_mbox->rollback_pending)
501
0
    imapc_mailbox_run_nofetch(ctx->src_mbox);
502
0
}
503
504
static void imapc_copy_bulk_ctx_deinit(struct imapc_save_context *ctx)
505
0
{
506
  /* Clean up the pending copy and the context attached to it */
507
0
  str_truncate(ctx->src_mbox->pending_copy_cmd, 0);
508
0
  i_free(ctx->src_mbox->copy_dest_box);
509
0
}
510
511
void imapc_transaction_save_rollback(struct mail_save_context *_ctx)
512
0
{
513
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
514
515
0
       if ((ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL) ||
516
0
     !ctx->finished) {
517
         /* There is still a pending copy which should not be send
518
      as rollback() is called or the transaction has not yet
519
      finished and rollback is called */
520
0
         ctx->failed = TRUE;
521
0
         (void)imapc_transaction_save_commit_pre(_ctx);
522
523
0
         i_assert(ctx->finished || ctx->src_mbox != NULL);
524
         /* Clean up the pending copy and the context attached to it */
525
0
         if (ctx->src_mbox != NULL) {
526
0
           if (ctx->src_mbox->pending_copy_request != NULL) {
527
0
             seqset_builder_deinit(&ctx->src_mbox->pending_copy_request->uidset_builder);
528
0
             i_free(ctx->src_mbox->pending_copy_request);
529
0
           }
530
0
           imapc_copy_bulk_ctx_deinit(ctx);
531
0
           imapc_client_stop(ctx->src_mbox->storage->client->client);
532
0
         }
533
0
       }
534
535
  /* Expunge all added messages from index */
536
0
  if (ctx->failed && array_is_created(&ctx->dest_saved_uids)) {
537
0
    i_assert(ctx->src_mbox != NULL);
538
0
    seq_range_array_merge(&ctx->src_mbox->copy_rollback_expunge_uids, &ctx->dest_saved_uids);
539
    /* Make sure context is not finished already */
540
0
    ctx->finished = FALSE;
541
0
    imapc_rollback_send_expunge(ctx);
542
0
    array_free(&ctx->dest_saved_uids);
543
0
  }
544
545
0
  if (ctx->finished || ctx->failed) {
546
0
    array_free(&ctx->dest_saved_uids);
547
0
    i_free(ctx);
548
0
  }
549
0
}
550
551
static bool imapc_save_copyuid(struct imapc_save_context *ctx,
552
             const struct imapc_command_reply *reply,
553
             uint32_t *uid_r)
554
0
{
555
0
  ARRAY_TYPE(seq_range) dest_uidset, source_uidset;
556
0
  struct seq_range_iter iter;
557
0
  const char *const *args;
558
0
  uint32_t uid_validity;
559
560
0
  *uid_r = 0;
561
562
  /* <uidvalidity> <source uid-set> <dest uid-set> */
563
0
  args = t_strsplit(reply->resp_text_value, " ");
564
0
  if (str_array_length(args) != 3)
565
0
    return FALSE;
566
567
0
  if (str_to_uint32(args[0], &uid_validity) < 0)
568
0
    return FALSE;
569
0
  if (ctx->dest_uid_validity == 0)
570
0
    ctx->dest_uid_validity = uid_validity;
571
0
  else if (ctx->dest_uid_validity != uid_validity)
572
0
    return FALSE;
573
574
0
  t_array_init(&source_uidset, 8);
575
0
  t_array_init(&dest_uidset, 8);
576
577
0
  if (imap_seq_set_nostar_parse(args[1], &source_uidset) < 0)
578
0
    return FALSE;
579
0
  if (imap_seq_set_nostar_parse(args[2], &dest_uidset) < 0)
580
0
    return FALSE;
581
582
0
  if (!array_is_created(&ctx->dest_saved_uids))
583
0
    i_array_init(&ctx->dest_saved_uids, 8);
584
585
0
  seq_range_array_merge(&ctx->dest_saved_uids, &dest_uidset);
586
587
0
  seq_range_array_iter_init(&iter, &dest_uidset);
588
0
  (void)seq_range_array_iter_nth(&iter, 0, uid_r);
589
0
  return TRUE;
590
0
}
591
592
static void imapc_copy_set_error(struct imapc_save_context *sctx,
593
         const struct imapc_command_reply *reply)
594
0
{
595
0
  sctx->failed = TRUE;
596
597
0
  if (reply->state != IMAPC_COMMAND_STATE_BAD)
598
0
    imapc_copy_error_from_reply(sctx->mbox->storage,
599
0
              MAIL_ERROR_PARAMS, reply);
600
0
  else
601
0
    mailbox_set_critical(&sctx->mbox->box,
602
0
             "imapc: COPY failed: %s",
603
0
             reply->text_full);
604
0
}
605
606
static void
607
imapc_copy_simple_callback(const struct imapc_command_reply *reply,
608
         void *context)
609
0
{
610
0
  struct imapc_save_cmd_context *ctx = context;
611
0
  uint32_t uid = 0;
612
613
0
  if (reply->state == IMAPC_COMMAND_STATE_OK) {
614
0
    if (reply->resp_text_key != NULL &&
615
0
        strcasecmp(reply->resp_text_key, "COPYUID") == 0)
616
0
      imapc_save_copyuid(ctx->ctx, reply, &uid);
617
0
    imapc_save_add_to_index(ctx->ctx, uid);
618
0
    ctx->ret = 0;
619
0
  } else if (reply->state == IMAPC_COMMAND_STATE_NO) {
620
0
    imapc_copy_error_from_reply(ctx->ctx->mbox->storage,
621
0
              MAIL_ERROR_PARAMS, reply);
622
0
    ctx->ret = -1;
623
0
  } else {
624
0
    mailbox_set_critical(&ctx->ctx->mbox->box,
625
0
      "imapc: COPY failed: %s", reply->text_full);
626
0
    ctx->ret = -1;
627
0
  }
628
0
  imapc_client_stop(ctx->ctx->mbox->storage->client->client);
629
0
}
630
631
static int
632
imapc_copy_simple(struct mail_save_context *_ctx, struct mail *mail)
633
0
{
634
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
635
0
  struct mailbox_transaction_context *_t = _ctx->transaction;
636
0
  struct imapc_save_cmd_context sctx;
637
0
  struct imapc_command *cmd;
638
639
0
  sctx.ret = -2;
640
0
  sctx.ctx = ctx;
641
0
  cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box,
642
0
               imapc_copy_simple_callback,
643
0
               &sctx);
644
0
  imapc_command_sendf(cmd, "UID COPY %u %s", mail->uid, _t->box->name);
645
0
  while (sctx.ret == -2)
646
0
    imapc_mailbox_run(ctx->src_mbox);
647
0
  ctx->finished = TRUE;
648
0
  return sctx.ret;
649
0
}
650
651
static void imapc_copy_bulk_callback(const struct imapc_command_reply *reply,
652
             void *context)
653
0
{
654
0
  struct imapc_copy_request *request = context;
655
0
  struct imapc_save_context *ctx = request->sctx;
656
0
  struct imapc_mailbox *mbox = ctx->src_mbox;
657
0
  unsigned int uid;
658
659
0
  i_assert(mbox != NULL);
660
0
  i_assert(request == mbox->pending_copy_request);
661
662
  /* Check the reply state and add uid's to index and
663
     dest_saved_uids. */
664
0
  if (ctx->failed) {
665
    /* If the saving already failed try to find UIDs already
666
       copied from the reply so that rollback can expunge
667
       them */
668
0
    if (null_strcasecmp(reply->resp_text_key, "COPYUID") == 0) {
669
0
      (void)imapc_save_copyuid(ctx, reply, &uid);
670
0
      imapc_transaction_save_rollback(&ctx->ctx);
671
0
    }
672
0
  } else if (reply->state == IMAPC_COMMAND_STATE_OK) {
673
0
    if (reply->resp_text_key != NULL &&
674
0
       strcasecmp(reply->resp_text_key, "COPYUID") == 0 &&
675
0
       imapc_save_copyuid(ctx, reply, &uid)) {
676
0
      ctx->finished = TRUE;
677
0
    }
678
0
  } else {
679
0
    imapc_copy_set_error(ctx, reply);
680
0
  }
681
682
0
  ctx->src_mbox->pending_copy_request = NULL;
683
0
  i_free(request);
684
0
  imapc_client_stop(mbox->storage->client->client);
685
0
}
686
687
static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox)
688
0
{
689
0
  struct imapc_command *cmd;
690
691
0
  i_assert(mbox != NULL);
692
0
  i_assert(mbox->pending_copy_request != NULL);
693
0
  i_assert(mbox->client_box != NULL);
694
695
0
  cmd = imapc_client_mailbox_cmd(mbox->client_box,
696
0
               imapc_copy_bulk_callback,
697
0
               mbox->pending_copy_request);
698
699
0
  seqset_builder_deinit(&mbox->pending_copy_request->uidset_builder);
700
701
0
  str_append(mbox->pending_copy_cmd, " ");
702
0
  imap_append_astring(mbox->pending_copy_cmd, mbox->copy_dest_box, 0);
703
704
0
  imapc_command_send(cmd, str_c(mbox->pending_copy_cmd));
705
706
0
  imapc_copy_bulk_ctx_deinit(mbox->pending_copy_request->sctx);
707
0
}
708
709
static bool
710
imapc_mail_copy_bulk_try_merge(struct imapc_mailbox *mbox, uint32_t uid,
711
             const char *box)
712
0
{
713
0
  i_assert(str_begins_with(str_c(mbox->pending_copy_cmd), "UID COPY "));
714
715
0
  if (strcmp(box, mbox->copy_dest_box) != 0) {
716
    /* Not the same mailbox merging not possible */
717
0
    return FALSE;
718
0
  }
719
0
  return seqset_builder_try_add(mbox->pending_copy_request->uidset_builder,
720
0
              IMAPC_SERVER_CMDLINE_MAX_LEN, uid);
721
0
}
722
723
static void
724
imapc_mail_copy_bulk_delayed_send_or_merge(struct imapc_save_context *ctx,
725
             uint32_t uid,
726
             const char *box)
727
0
{
728
0
  struct imapc_mailbox *mbox = ctx->src_mbox;
729
730
0
  if (mbox->pending_copy_request != NULL &&
731
0
      !imapc_mail_copy_bulk_try_merge(mbox, uid, box)) {
732
    /* send the previous COPY and create new one after
733
       waiting for this one to be finished. */
734
0
    imapc_mail_copy_bulk_flush(mbox);
735
0
    imapc_copy_bulk_finish(mbox->pending_copy_request->sctx);
736
0
  }
737
0
  if (mbox->pending_copy_request == NULL) {
738
0
    mbox->pending_copy_request =
739
0
      i_new(struct imapc_copy_request, 1);
740
0
    str_printfa(mbox->pending_copy_cmd, "UID COPY ");
741
0
    mbox->pending_copy_request->uidset_builder =
742
0
      seqset_builder_init(mbox->pending_copy_cmd);
743
0
    seqset_builder_add(mbox->pending_copy_request->uidset_builder,
744
0
           uid);
745
0
    mbox->copy_dest_box = i_strdup(box);
746
0
  } else {
747
0
    i_assert(mbox->pending_copy_request->sctx == ctx);
748
0
  }
749
0
  mbox->pending_copy_request->sctx = ctx;
750
0
}
751
752
static int
753
imapc_copy_bulk(struct imapc_save_context *ctx, struct mail *mail)
754
0
{
755
0
  struct imapc_mailbox *mbox = IMAPC_MAILBOX(ctx->ctx.transaction->box);
756
757
0
  imapc_mail_copy_bulk_delayed_send_or_merge(ctx, mail->uid,
758
0
               imapc_mailbox_get_remote_name(mbox));
759
0
  imapc_save_add_to_index(ctx, 0);
760
761
0
  return ctx->failed ? -1 : 0;
762
0
}
763
764
static bool imapc_is_mail_expunged(struct imapc_mailbox *mbox, uint32_t uid)
765
0
{
766
0
  if (array_is_created(&mbox->delayed_expunged_uids) &&
767
0
      seq_range_exists(&mbox->delayed_expunged_uids, uid))
768
0
    return TRUE;
769
0
  if (mbox->delayed_sync_trans == NULL)
770
0
    return FALSE;
771
772
0
  struct mail_index_view *view =
773
0
    mail_index_transaction_get_view(mbox->delayed_sync_trans);
774
0
  uint32_t seq;
775
0
  return mail_index_lookup_seq(view, uid, &seq) &&
776
0
    mail_index_transaction_is_expunged(mbox->delayed_sync_trans, seq);
777
0
}
778
779
int imapc_copy(struct mail_save_context *_ctx, struct mail *mail)
780
0
{
781
0
  struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx);
782
0
  struct mailbox_transaction_context *_t = _ctx->transaction;
783
0
  struct imapc_msgmap *src_msgmap;
784
0
  uint32_t rseq;
785
0
  int ret;
786
787
0
  i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
788
789
0
  if (_t->box->storage == mail->box->storage) {
790
    /* Currently we don't support copying mails from multiple
791
       different source mailboxes within the same transaction. */
792
0
    i_assert(ctx->src_mbox == NULL || &ctx->src_mbox->box == mail->box);
793
0
    ctx->src_mbox = IMAPC_MAILBOX(mail->box);
794
0
    if (!mail->expunged &&
795
0
        imapc_is_mail_expunged(ctx->src_mbox, mail->uid))
796
0
      mail_set_expunged(mail);
797
    /* same server, we can use COPY for the mail */
798
0
    src_msgmap =
799
0
      imapc_client_mailbox_get_msgmap(ctx->src_mbox->client_box);
800
0
    if (mail->expunged ||
801
0
        !imapc_msgmap_uid_to_rseq(src_msgmap, mail->uid, &rseq)) {
802
0
      mail_storage_set_error(mail->box->storage,
803
0
                 MAIL_ERROR_EXPUNGED,
804
0
                 "Some of the requested messages no longer exist.");
805
0
      ctx->finished = TRUE;
806
0
      index_save_context_free(_ctx);
807
0
      return -1;
808
0
    }
809
    /* Mail has not been expunged and can be copied. */
810
0
    if (ctx->mbox->capabilities == 0) {
811
      /* The destination mailbox has not yet been selected
812
         so the capabilities are unknown */
813
0
      if (imapc_client_get_capabilities(ctx->mbox->storage->client->client,
814
0
                  &ctx->mbox->capabilities) < 0) {
815
0
        mail_storage_set_error(mail->box->storage,
816
0
                   MAIL_ERROR_UNAVAILABLE,
817
0
                   "Failed to determine capabilities for mailbox.");
818
0
        ctx->finished = TRUE;
819
0
        index_save_context_free(_ctx);
820
0
        return -1;
821
0
      }
822
0
    }
823
0
    if ((ctx->mbox->capabilities & IMAPC_CAPABILITY_UIDPLUS) != 0)
824
0
      ret = imapc_copy_bulk(ctx, mail);
825
0
    else
826
0
      ret = imapc_copy_simple(_ctx, mail);
827
0
    index_save_context_free(_ctx);
828
0
    return ret;
829
0
  }
830
0
  return mail_storage_copy(_ctx, mail);
831
0
}