IMAPC_INPUT_STATE_SKIPLINE
};
+struct imapc_command_stream {
+ unsigned int pos;
+ struct istream *input;
+};
+
struct imapc_command {
pool_t pool;
buffer_t *data;
unsigned int send_pos;
unsigned int tag;
+ ARRAY_DEFINE(streams, struct imapc_command_stream);
+
imapc_command_callback_t *callback;
void *context;
};
static void imapc_connection_disconnect(struct imapc_connection *conn);
+static void imapc_command_free(struct imapc_command *cmd);
static void imapc_command_send_more(struct imapc_connection *conn,
struct imapc_command *cmd);
array_delete(&conn->cmd_wait_list, 0, 1);
cmd->callback(&reply, cmd->context);
- pool_unref(&cmd->pool);
+ imapc_command_free(cmd);
}
while (array_count(&conn->cmd_send_queue) > 0) {
cmdp = array_idx(&conn->cmd_send_queue, 0);
array_delete(&conn->cmd_send_queue, 0, 1);
cmd->callback(&reply, cmd->context);
- pool_unref(&cmd->pool);
+ imapc_command_free(cmd);
}
}
if (state == IMAPC_CONNECTION_STATE_DONE) {
imapc_connection_input_reset(conn);
cmd->callback(&reply, cmd->context);
- pool_unref(&cmd->pool);
+ imapc_command_free(cmd);
return 1;
}
return cmd;
}
+static void imapc_command_free(struct imapc_command *cmd)
+{
+ struct imapc_command_stream *stream;
+
+ if (array_is_created(&cmd->streams)) {
+ array_foreach_modifiable(&cmd->streams, stream)
+ i_stream_unref(&stream->input);
+ }
+ pool_unref(&cmd->pool);
+}
+
static bool
parse_sync_literal(const unsigned char *data, unsigned int pos,
unsigned int *value_r)
return TRUE;
}
+static void imapc_command_send_done(struct imapc_connection *conn,
+ struct imapc_command *cmd)
+{
+ /* everything sent. move command to wait list. */
+ i_assert(*array_idx(&conn->cmd_send_queue, 0) == cmd);
+ array_delete(&conn->cmd_send_queue, 0, 1);
+ array_append(&conn->cmd_wait_list, &cmd, 1);
+
+ if (array_count(&conn->cmd_send_queue) > 0 &&
+ conn->state == IMAPC_CONNECTION_STATE_DONE) {
+ /* send the next command in queue */
+ struct imapc_command *const *cmd2_p =
+ array_idx(&conn->cmd_send_queue, 0);
+ imapc_command_send_more(conn, *cmd2_p);
+ }
+}
+
+static int imapc_command_try_send_stream(struct imapc_connection *conn,
+ struct imapc_command *cmd)
+{
+ struct imapc_command_stream *stream;
+
+ if (!array_is_created(&cmd->streams) || array_count(&cmd->streams) == 0)
+ return -1;
+
+ stream = array_idx_modifiable(&cmd->streams, 0);
+ if (stream->pos != cmd->send_pos)
+ return -1;
+
+ /* we're sending the stream now */
+ (void)o_stream_send_istream(conn->output, stream->input);
+ if (!i_stream_is_eof(stream->input))
+ return 0;
+
+ /* finished with the stream */
+ i_stream_unref(&stream->input);
+ array_delete(&cmd->streams, 0, 1);
+
+ i_assert(cmd->send_pos != cmd->data->used);
+ return 1;
+}
+
static void imapc_command_send_more(struct imapc_connection *conn,
struct imapc_command *cmd)
{
const unsigned char *p;
unsigned int seek_pos, start_pos, end_pos, size;
+ int ret;
i_assert(cmd->send_pos < cmd->data->used);
+ if ((ret = imapc_command_try_send_stream(conn, cmd)) == 0)
+ return;
+
seek_pos = cmd->send_pos;
- if (seek_pos != 0) {
+ if (seek_pos != 0 && ret < 0) {
/* skip over the literal. we can also get here from
AUTHENTICATE command, which doesn't use a literal */
if (parse_sync_literal(cmd->data->data, seek_pos, &size)) {
cmd->send_pos = end_pos;
if (cmd->send_pos == cmd->data->used) {
- /* everything sent. move command to wait list. */
- i_assert(*array_idx(&conn->cmd_send_queue, 0) == cmd);
- array_delete(&conn->cmd_send_queue, 0, 1);
- array_append(&conn->cmd_wait_list, &cmd, 1);
-
- if (array_count(&conn->cmd_send_queue) > 0 &&
- conn->state == IMAPC_CONNECTION_STATE_DONE) {
- /* send the next command in queue */
- struct imapc_command *const *cmd2_p =
- array_idx(&conn->cmd_send_queue, 0);
- imapc_command_send_more(conn, *cmd2_p);
- }
+ i_assert(!array_is_created(&cmd->streams) ||
+ array_count(&cmd->streams) == 0);
+ imapc_command_send_done(conn, cmd);
}
}
str_printfa(cmd->data, "%u", arg);
break;
}
+ case 'p': {
+ struct istream *input = va_arg(args, struct istream *);
+ struct imapc_command_stream *s;
+ uoff_t size;
+
+ if (!array_is_created(&cmd->streams))
+ p_array_init(&cmd->streams, cmd->pool, 2);
+ if (i_stream_get_size(input, TRUE, &size) < 0)
+ size = 0;
+ str_printfa(cmd->data, "{%"PRIuSIZE_T"}\r\n", size);
+ s = array_append_space(&cmd->streams);
+ s->pos = str_len(cmd->data);
+ s->input = input;
+ i_stream_ref(input);
+ break;
+ }
case 's': {
const char *arg = va_arg(args, const char *);
mail_index_transaction_open_updated_view(mbox->delayed_sync_trans);
}
+static void
+imapc_newmsgs_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_mailbox *mbox = context;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK)
+ ;
+ else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(mbox->storage, MAIL_ERROR_PARAMS,
+ reply);
+ } else {
+ mail_storage_set_critical(&mbox->storage->storage,
+ "imapc: Command failed: %s", reply->text_full);
+ }
+ if (mbox->opening)
+ imapc_client_stop(mbox->storage->client);
+}
+
static void imapc_untagged_exists(const struct imapc_untagged_reply *reply,
struct imapc_mailbox *mbox)
{
hdr = mail_index_get_header(mbox->box.view);
mbox->new_msgs = TRUE;
- imapc_client_mailbox_cmdf(mbox->client_box, imapc_async_stop_callback,
- mbox->storage, "UID FETCH %u:* FLAGS",
- hdr->next_uid);
+ imapc_client_mailbox_cmdf(mbox->client_box, imapc_newmsgs_callback,
+ mbox, "UID FETCH %u:* FLAGS", hdr->next_uid);
}
static void imapc_mailbox_idle_timeout(struct imapc_mailbox *mbox)
-/* Copyright (c) 2007-2010 Dovecot authors, see the included COPYING file */
+/* Copyright (c) 2011 Dovecot authors, see the included COPYING file */
#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "istream-crlf.h"
+#include "ostream.h"
+#include "imap-date.h"
+#include "imap-util.h"
#include "index-mail.h"
+#include "mail-copy.h"
+#include "imapc-client.h"
#include "imapc-storage.h"
#include "imapc-sync.h"
struct imapc_mailbox *mbox;
struct mail_index_transaction *trans;
+ int fd;
+ char *temp_path;
+ struct istream *input;
+
+ uint32_t dest_uid_validity;
+ ARRAY_TYPE(seq_range) dest_saved_uids;
+
unsigned int failed:1;
+ unsigned int finished:1;
+};
+
+struct imapc_save_cmd_context {
+ struct imapc_save_context *ctx;
+ int ret;
};
+void imapc_transaction_save_rollback(struct mail_save_context *_ctx);
+
struct mail_save_context *
imapc_save_alloc(struct mailbox_transaction_context *t)
{
ctx->ctx.transaction = t;
ctx->mbox = mbox;
ctx->trans = t->itrans;
+ ctx->fd = -1;
t->save_ctx = &ctx->ctx;
}
return t->save_ctx;
int imapc_save_begin(struct mail_save_context *_ctx, struct istream *input)
{
- return -1;
+ struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+ struct mail_storage *storage = _ctx->transaction->box->storage;
+ const char *path;
+
+ i_assert(ctx->fd == -1);
+
+ ctx->fd = imapc_create_temp_fd(storage->user, &path);
+ if (ctx->fd == -1) {
+ mail_storage_set_critical(storage,
+ "Couldn't create temp file %s", path);
+ ctx->failed = TRUE;
+ return -1;
+ }
+ /* we may not know the size of the input, or be sure that it contains
+ only CRLFs. so we'll always first write the mail to a temp file and
+ upload it from there to remote server. */
+ ctx->finished = FALSE;
+ ctx->temp_path = i_strdup(path);
+ ctx->input = i_stream_create_crlf(input);
+ _ctx->output = o_stream_create_fd_file(ctx->fd, 0, FALSE);
+ o_stream_cork(_ctx->output);
+ return 0;
}
int imapc_save_continue(struct mail_save_context *_ctx)
{
- return -1;
+ struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+ struct mail_storage *storage = _ctx->transaction->box->storage;
+
+ if (ctx->failed)
+ return -1;
+
+ if (o_stream_send_istream(_ctx->output, ctx->input) < 0) {
+ if (!mail_storage_set_error_from_errno(storage)) {
+ mail_storage_set_critical(storage,
+ "o_stream_send_istream(%s) failed: %m",
+ ctx->temp_path);
+ }
+ ctx->failed = TRUE;
+ return -1;
+ }
+ return 0;
+}
+
+static void imapc_save_appenduid(struct imapc_save_context *ctx,
+ const struct imapc_command_reply *reply)
+{
+ const char *const *args;
+ uint32_t uid_validity, dest_uid;
+
+ /* <uidvalidity> <dest uid-set> */
+ args = t_strsplit(reply->resp_text_value, " ");
+ if (str_array_length(args) != 2)
+ return;
+
+ if (str_to_uint32(args[0], &uid_validity) < 0)
+ return;
+ if (ctx->dest_uid_validity == 0)
+ ctx->dest_uid_validity = uid_validity;
+ else if (ctx->dest_uid_validity != uid_validity)
+ return;
+
+ if (str_to_uint32(args[1], &dest_uid) == 0)
+ seq_range_array_add(&ctx->dest_saved_uids, 0, dest_uid);
+}
+
+static void imapc_save_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_save_cmd_context *ctx = context;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ if (strcasecmp(reply->resp_text_key, "APPENDUID") == 0)
+ imapc_save_appenduid(ctx->ctx, reply);
+ ctx->ret = 0;
+ } else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(ctx->ctx->mbox->storage,
+ MAIL_ERROR_PARAMS, reply);
+ ctx->ret = -1;
+ } else {
+ mail_storage_set_critical(&ctx->ctx->mbox->storage->storage,
+ "imapc: COPY failed: %s", reply->text_full);
+ ctx->ret = -1;
+ }
+ imapc_client_stop(ctx->ctx->mbox->storage->client);
+}
+
+static void
+imapc_append_keywords(string_t *str, struct mail_keywords *kw)
+{
+ const ARRAY_TYPE(keywords) *kw_arr;
+ const char *const *kw_p;
+ unsigned int i;
+
+ kw_arr = mail_index_get_keywords(kw->index);
+ for (i = 0; i < kw->count; i++) {
+ kw_p = array_idx(kw_arr, kw->idx[i]);
+ if (str_len(str) > 1)
+ str_append_c(str, ' ');
+ str_append(str, *kw_p);
+ }
+}
+
+static int imapc_save_append(struct imapc_save_context *ctx)
+{
+ struct mail_save_context *_ctx = &ctx->ctx;
+ struct imapc_save_cmd_context sctx;
+ struct istream *input;
+ const char *flags = "", *internaldate = "";
+
+ if (_ctx->flags != 0 || _ctx->keywords != NULL) {
+ string_t *str = t_str_new(64);
+
+ str_append(str, " (");
+ imap_write_flags(str, _ctx->flags, NULL);
+ if (_ctx->keywords != NULL)
+ imapc_append_keywords(str, _ctx->keywords);
+ str_append_c(str, ')');
+ flags = str_c(str);
+ }
+ if (_ctx->received_date != (time_t)-1) {
+ internaldate = t_strdup_printf(" \"%s\"",
+ imap_to_datetime(_ctx->received_date));
+ }
+
+ input = i_stream_create_fd(ctx->fd, IO_BLOCK_SIZE, FALSE);
+ sctx.ctx = ctx;
+ imapc_client_cmdf(ctx->mbox->storage->client,
+ imapc_save_callback, &sctx, "APPEND %s%1s%1s %p",
+ ctx->mbox->box.name, flags, internaldate, input);
+ i_stream_unref(&input);
+ imapc_client_run(ctx->mbox->storage->client);
+ return sctx.ret;
}
int imapc_save_finish(struct mail_save_context *_ctx)
{
struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+ struct mail_storage *storage = _ctx->transaction->box->storage;
- ctx->failed = TRUE;
+ ctx->finished = TRUE;
+ if (!ctx->failed) {
+ if (o_stream_flush(_ctx->output) < 0) {
+ if (!mail_storage_set_error_from_errno(storage)) {
+ mail_storage_set_critical(storage,
+ "o_stream_flush(%s) failed: %m",
+ ctx->temp_path);
+ }
+ ctx->failed = TRUE;
+ }
+ }
+
+ if (!ctx->failed) {
+ if (imapc_save_append(ctx) < 0)
+ ctx->failed = TRUE;
+ }
+
+ if (_ctx->output != NULL)
+ o_stream_unref(&_ctx->output);
+ if (ctx->input != NULL)
+ i_stream_unref(&ctx->input);
+ if (ctx->fd != -1) {
+ if (close(ctx->fd) < 0)
+ i_error("close(%s) failed: %m", ctx->temp_path);
+ ctx->fd = -1;
+ }
+ i_free(ctx->temp_path);
index_save_context_free(_ctx);
return ctx->failed ? -1 : 0;
}
int imapc_transaction_save_commit_pre(struct mail_save_context *_ctx)
{
- return -1;
+ struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+ struct mail_transaction_commit_changes *changes =
+ _ctx->transaction->changes;
+
+ i_assert(ctx->finished);
+
+ if (array_is_created(&ctx->dest_saved_uids)) {
+ changes->uid_validity = ctx->dest_uid_validity;
+ array_append_array(&changes->saved_uids, &ctx->dest_saved_uids);
+ }
+ return 0;
}
void imapc_transaction_save_commit_post(struct mail_save_context *_ctx,
- struct mail_index_transaction_commit_result *result)
+ struct mail_index_transaction_commit_result *result ATTR_UNUSED)
{
+ imapc_transaction_save_rollback(_ctx);
}
void imapc_transaction_save_rollback(struct mail_save_context *_ctx)
{
+ struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+
+ /* FIXME: if we really want to rollback, we should expunge messages
+ we already saved */
+
+ if (!ctx->finished)
+ imapc_save_cancel(_ctx);
+
+ if (array_is_created(&ctx->dest_saved_uids))
+ array_free(&ctx->dest_saved_uids);
+ i_free(ctx);
+}
+
+static void imapc_save_copyuid(struct imapc_save_context *ctx,
+ const struct imapc_command_reply *reply)
+{
+ const char *const *args;
+ uint32_t uid_validity, dest_uid;
+
+ /* <uidvalidity> <source uid-set> <dest uid-set> */
+ args = t_strsplit(reply->resp_text_value, " ");
+ if (str_array_length(args) != 3)
+ return;
+
+ if (str_to_uint32(args[0], &uid_validity) < 0)
+ return;
+ if (ctx->dest_uid_validity == 0)
+ ctx->dest_uid_validity = uid_validity;
+ else if (ctx->dest_uid_validity != uid_validity)
+ return;
+
+ if (str_to_uint32(args[2], &dest_uid) == 0)
+ seq_range_array_add(&ctx->dest_saved_uids, 0, dest_uid);
+}
+
+static void imapc_copy_callback(const struct imapc_command_reply *reply,
+ void *context)
+{
+ struct imapc_save_cmd_context *ctx = context;
+
+ if (reply->state == IMAPC_COMMAND_STATE_OK) {
+ if (strcasecmp(reply->resp_text_key, "COPYUID") == 0)
+ imapc_save_copyuid(ctx->ctx, reply);
+ ctx->ret = 0;
+ } else if (reply->state == IMAPC_COMMAND_STATE_NO) {
+ imapc_copy_error_from_reply(ctx->ctx->mbox->storage,
+ MAIL_ERROR_PARAMS, reply);
+ ctx->ret = -1;
+ } else {
+ mail_storage_set_critical(&ctx->ctx->mbox->storage->storage,
+ "imapc: COPY failed: %s", reply->text_full);
+ ctx->ret = -1;
+ }
+ imapc_client_stop(ctx->ctx->mbox->storage->client);
+}
+
+int imapc_copy(struct mail_save_context *_ctx, struct mail *mail)
+{
+ struct imapc_save_context *ctx = (struct imapc_save_context *)_ctx;
+ struct mailbox_transaction_context *_t = _ctx->transaction;
+ struct imapc_mailbox *src_mbox = (struct imapc_mailbox *)mail->box;
+ struct imapc_save_cmd_context sctx;
+
+ i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0);
+
+ if (_t->box->storage == mail->box->storage) {
+ /* same server, we can use COPY for the mail */
+ sctx.ret = -2;
+ sctx.ctx = ctx;
+ imapc_client_mailbox_cmdf(src_mbox->client_box,
+ imapc_copy_callback, &sctx,
+ "UID COPY %u %s",
+ mail->uid, _t->box->name);
+ imapc_client_run(src_mbox->storage->client);
+ i_assert(sctx.ret != -2);
+ ctx->finished = TRUE;
+ return sctx.ret;
+ }
+ return mail_storage_copy(_ctx, mail);
}
#include "lib.h"
#include "istream.h"
-#include "safe-mkstemp.h"
#include "write-full.h"
#include "str.h"
#include "imap-arg.h"
return TRUE;
}
-static int create_temp_fd(struct mail_user *user, const char **path_r)
-{
- string_t *path;
- int fd;
-
- path = t_str_new(128);
- mail_user_set_get_temp_prefix(path, user->set);
- fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
- if (fd == -1) {
- i_error("safe_mkstemp(%s) failed: %m", str_c(path));
- return -1;
- }
-
- /* we just want the fd, unlink it */
- if (unlink(str_c(path)) < 0) {
- /* shouldn't happen.. */
- i_error("unlink(%s) failed: %m", str_c(path));
- (void)close(fd);
- return -1;
- }
- *path_r = str_c(path);
- return fd;
-}
-
static void
imapc_fetch_stream(struct index_mail *imail, const char *value, bool body)
{
if (imail->data.stream != NULL)
return;
- fd = create_temp_fd(_mail->box->storage->user, &path);
+ fd = imapc_create_temp_fd(_mail->box->storage->user, &path);
if (fd == -1)
return;
if (write_full(fd, value, value_len) < 0) {
#include "lib.h"
#include "ioloop.h"
#include "str.h"
+#include "safe-mkstemp.h"
#include "imap-arg.h"
#include "imap-resp-code.h"
-#include "mail-copy.h"
#include "index-mail.h"
#include "imapc-client.h"
#include "imapc-list.h"
return &storage->storage;
}
-static void
-imapc_copy_error_from_reply(struct imapc_storage *storage,
- enum mail_error default_error,
- const struct imapc_command_reply *reply)
+void imapc_copy_error_from_reply(struct imapc_storage *storage,
+ enum mail_error default_error,
+ const struct imapc_command_reply *reply)
{
enum mail_error error;
if (index_storage_mailbox_open(box, FALSE) < 0)
return -1;
- if (box->deleting) {
+ if (box->deleting || (box->flags & MAILBOX_FLAG_SAVEONLY) != 0) {
/* We don't actually want to SELECT the mailbox. */
return 0;
}
+ mbox->opening = TRUE;
ctx.mbox = mbox;
ctx.ret = -1;
mbox->client_box =
imapc_mailbox_open_callback,
&ctx, mbox);
imapc_client_run(mbox->storage->client);
+ mbox->opening = FALSE;
if (ctx.ret < 0) {
mailbox_close(box);
return -1;
/* we're doing IDLE all the time anyway - nothing to do here */
}
+int imapc_create_temp_fd(struct mail_user *user, const char **path_r)
+{
+ string_t *path;
+ int fd;
+
+ path = t_str_new(128);
+ mail_user_set_get_temp_prefix(path, user->set);
+ fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1);
+ if (fd == -1) {
+ i_error("safe_mkstemp(%s) failed: %m", str_c(path));
+ return -1;
+ }
+
+ /* we just want the fd, unlink it */
+ if (unlink(str_c(path)) < 0) {
+ /* shouldn't happen.. */
+ i_error("unlink(%s) failed: %m", str_c(path));
+ (void)close(fd);
+ return -1;
+ }
+ *path_r = str_c(path);
+ return fd;
+}
+
struct mail_storage imapc_storage = {
.name = IMAPC_STORAGE_NAME,
.class_flags = 0,
imapc_save_continue,
imapc_save_finish,
imapc_save_cancel,
- mail_storage_copy,
+ imapc_copy,
index_storage_is_inconsistent
}
};
ARRAY_DEFINE(untagged_callbacks, struct imapc_mailbox_event_callback);
ARRAY_DEFINE(resp_text_callbacks, struct imapc_mailbox_event_callback);
+ unsigned int opening:1;
unsigned int new_msgs:1;
};
int imapc_save_continue(struct mail_save_context *ctx);
int imapc_save_finish(struct mail_save_context *ctx);
void imapc_save_cancel(struct mail_save_context *ctx);
+int imapc_copy(struct mail_save_context *ctx, struct mail *mail);
int imapc_transaction_save_commit_pre(struct mail_save_context *ctx);
void imapc_transaction_save_commit_post(struct mail_save_context *ctx,
struct mail *mail, bool *tryagain_r);
void imapc_fetch_mail_update(struct mail *mail, const struct imap_arg *args);
+void imapc_copy_error_from_reply(struct imapc_storage *storage,
+ enum mail_error default_error,
+ const struct imapc_command_reply *reply);
void imapc_simple_callback(const struct imapc_command_reply *reply,
void *context);
void imapc_async_stop_callback(const struct imapc_command_reply *reply,
imapc_mailbox_callback_t *callback);
void imapc_mailbox_register_callbacks(struct imapc_mailbox *mbox);
+int imapc_create_temp_fd(struct mail_user *user, const char **path_r);
#endif