imap-fetch.c revision f036083a96932811a7201017597415e262413725
a8c5a86d183db25a57bf193c06b41e092ec2e151Timo Sirainen/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "common.h"
d6499957ea59e6d9729d3350d9ac5eae992635f6Timo Sirainen#include "array.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "buffer.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "istream.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "ostream.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "str.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "message-send.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "message-size.h"
de62ce819d59a529530da4b57be1b8d6dad13d6bTimo Sirainen#include "imap-date.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "mail-search-build.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "commands.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "imap-fetch.h"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include "imap-util.h"
de62ce819d59a529530da4b57be1b8d6dad13d6bTimo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen#include <stdlib.h>
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen#define BODY_NIL_REPLY \
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen "\"text\" \"plain\" NIL NIL NIL \"7bit\" 0 0 NIL NIL NIL"
4c0aff96fc7e6d779be43458f96cbf015849a3deTimo Sirainen#define ENVELOPE_NIL_REPLY \
4c0aff96fc7e6d779be43458f96cbf015849a3deTimo Sirainen "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)"
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainenconst struct imap_fetch_handler default_handlers[8];
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainenstatic buffer_t *fetch_handlers = NULL;
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen
4c0aff96fc7e6d779be43458f96cbf015849a3deTimo Sirainenstatic int imap_fetch_handler_cmp(const void *p1, const void *p2)
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen{
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen const struct imap_fetch_handler *h1 = p1, *h2 = p2;
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen return strcmp(h1->name, h2->name);
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen}
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainenvoid imap_fetch_handlers_register(const struct imap_fetch_handler *handlers,
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen size_t count)
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen{
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen void *data;
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen size_t size;
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen if (fetch_handlers == NULL)
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen fetch_handlers = buffer_create_dynamic(default_pool, 128);
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen buffer_append(fetch_handlers, handlers, sizeof(*handlers) * count);
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen data = buffer_get_modifiable_data(fetch_handlers, &size);
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen qsort(data, size / sizeof(*handlers), sizeof(*handlers),
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen imap_fetch_handler_cmp);
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen}
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainenstatic int imap_fetch_handler_bsearch(const void *name_p, const void *handler_p)
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen{
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen const char *name = name_p;
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen const struct imap_fetch_handler *h = handler_p;
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen int i;
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen for (i = 0; h->name[i] != '\0'; i++) {
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen if (h->name[i] != name[i]) {
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen if (name[i] < 'A' || name[i] >= 'Z')
d6499957ea59e6d9729d3350d9ac5eae992635f6Timo Sirainen return -1;
4c0aff96fc7e6d779be43458f96cbf015849a3deTimo Sirainen return name[i] - h->name[i];
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen }
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen }
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
77b5fd56e5a06d624f3ab92198272287333114f4Timo Sirainen return name[i] < 'A' || name[i] >= 'Z' ? 0 : -1;
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen}
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainenbool imap_fetch_init_handler(struct imap_fetch_context *ctx, const char *name,
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen const struct imap_arg **args)
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen{
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen const struct imap_fetch_handler *handler;
402e999a878e0cc41a0afb830fea0a93afc75f0dTimo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen handler = bsearch(name, fetch_handlers->data,
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen fetch_handlers->used /
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen sizeof(struct imap_fetch_handler),
402e999a878e0cc41a0afb830fea0a93afc75f0dTimo Sirainen sizeof(struct imap_fetch_handler),
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen imap_fetch_handler_bsearch);
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen if (handler == NULL) {
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen client_send_command_error(ctx->cmd,
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen t_strconcat("Unknown command ", name, NULL));
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen return FALSE;
402e999a878e0cc41a0afb830fea0a93afc75f0dTimo Sirainen }
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen
678d0463849ba777106eb7875f27db07a5d8e3dfTimo Sirainen return handler->init(ctx, name, args);
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen}
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainenstruct imap_fetch_context *
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainenimap_fetch_init(struct client_command_context *cmd, struct mailbox *box)
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen{
bd4e36a8cd7257cca7d1434c49a1e343ed7c5100Timo Sirainen struct client *client = cmd->client;
struct imap_fetch_context *ctx;
if (fetch_handlers == NULL) {
imap_fetch_handlers_register(default_handlers,
N_ELEMENTS(default_handlers));
}
ctx = p_new(cmd->pool, struct imap_fetch_context, 1);
ctx->client = client;
ctx->cmd = cmd;
ctx->box = box;
ctx->cur_str = str_new(default_pool, 8192);
ctx->all_headers_buf = buffer_create_dynamic(cmd->pool, 128);
p_array_init(&ctx->handlers, cmd->pool, 16);
p_array_init(&ctx->tmp_keywords, cmd->pool,
client->keywords.announce_count + 8);
ctx->line_finished = TRUE;
return ctx;
}
bool imap_fetch_add_unchanged_since(struct imap_fetch_context *ctx,
uint64_t modseq)
{
struct mail_search_arg *search_arg;
search_arg = p_new(ctx->search_args->pool, struct mail_search_arg, 1);
search_arg->type = SEARCH_MODSEQ;
search_arg->value.modseq =
p_new(ctx->cmd->pool, struct mail_search_modseq, 1);
search_arg->value.modseq->modseq = modseq;
search_arg->next = ctx->search_args->args->next;
ctx->search_args->args->next = search_arg;
return imap_fetch_init_handler(ctx, "MODSEQ", NULL);
}
#undef imap_fetch_add_handler
void imap_fetch_add_handler(struct imap_fetch_context *ctx,
bool buffered, bool want_deinit,
const char *name, const char *nil_reply,
imap_fetch_handler_t *handler, void *context)
{
/* partially because of broken clients, but also partially because
it potentially can make client implementations faster, we have a
buffered parameter which basically means that the handler promises
to write the output in ctx->cur_str. The cur_str is then sent to
client before calling any non-buffered handlers.
We try to keep the handler registration order the same as the
client requested them. This is especially useful to get UID
returned first, which some clients rely on..
*/
const struct imap_fetch_context_handler *handlers;
struct imap_fetch_context_handler h;
unsigned int i, size;
if (context == NULL) {
/* don't allow duplicate handlers */
handlers = array_get(&ctx->handlers, &size);
for (i = 0; i < size; i++) {
if (handlers[i].handler == handler &&
handlers[i].context == NULL)
return;
}
}
memset(&h, 0, sizeof(h));
h.handler = handler;
h.context = context;
h.buffered = buffered;
h.want_deinit = want_deinit;
h.name = p_strdup(ctx->cmd->pool, name);
h.nil_reply = p_strdup(ctx->cmd->pool, nil_reply);
if (!buffered)
array_append(&ctx->handlers, &h, 1);
else {
array_insert(&ctx->handlers, ctx->buffered_handlers_count,
&h, 1);
ctx->buffered_handlers_count++;
}
}
static void
expunges_drop_known(struct imap_fetch_context *ctx, struct mail *mail,
ARRAY_TYPE(seq_range) *expunges)
{
const uint32_t *seqs, *uids;
unsigned int i, count;
seqs = array_get(ctx->qresync_sample_seqset, &count);
uids = array_idx(ctx->qresync_sample_uidset, 0);
i_assert(array_count(ctx->qresync_sample_uidset) == count);
i_assert(count > 0);
/* FIXME: we could do removals from the middle as well */
for (i = 0; i < count; i++) {
mail_set_seq(mail, seqs[i]);
if (uids[i] != mail->uid)
break;
}
if (i > 0)
seq_range_array_remove_range(expunges, 1, uids[i-1]);
}
static int get_expunges_fallback(struct imap_fetch_context *ctx,
const ARRAY_TYPE(seq_range) *uids,
ARRAY_TYPE(seq_range) *expunges)
{
struct mailbox_transaction_context *trans;
struct mail_search_args *search_args;
struct mail_search_context *search_ctx;
struct mail *mail;
const struct seq_range *uid_range;
struct mailbox_status status;
unsigned int i, count;
uint32_t next_uid;
int ret = 0;
uid_range = array_get(uids, &count);
i_assert(count > 0);
i = 0;
next_uid = uid_range[0].seq1;
/* search UIDs in given range */
search_args = mail_search_build_init();
search_args->args = p_new(search_args->pool, struct mail_search_arg, 1);
search_args->args->type = SEARCH_UIDSET;
i_array_init(&search_args->args->value.seqset, array_count(uids));
array_append_array(&search_args->args->value.seqset, uids);
trans = mailbox_transaction_begin(ctx->box, 0);
mail = mail_alloc(trans, 0, NULL);
search_ctx = mailbox_search_init(trans, search_args, NULL);
mail_search_args_unref(&search_args);
while (mailbox_search_next(search_ctx, mail) > 0) {
if (mail->uid == next_uid) {
if (next_uid < uid_range[i].seq2)
next_uid++;
else if (++i < count)
next_uid = uid_range[++i].seq1;
else
break;
} else {
/* next_uid .. mail->uid-1 are expunged */
i_assert(mail->uid > next_uid);
while (mail->uid > uid_range[i].seq2) {
seq_range_array_add_range(expunges, next_uid,
uid_range[i].seq2);
i++;
i_assert(i < count);
next_uid = uid_range[i].seq1;
}
if (next_uid != mail->uid) {
seq_range_array_add_range(expunges, next_uid,
mail->uid - 1);
}
if (uid_range[i].seq2 == mail->uid)
next_uid = uid_range[++i].seq1;
else
next_uid = mail->uid + 1;
}
}
if (i < count) {
i_assert(next_uid <= uid_range[i].seq2);
seq_range_array_add_range(expunges, next_uid,
uid_range[i].seq2);
i++;
}
for (; i < count; i++) {
seq_range_array_add_range(expunges, uid_range[i].seq1,
uid_range[i].seq2);
}
mailbox_get_status(ctx->box, STATUS_UIDNEXT, &status);
seq_range_array_remove_range(expunges, status.uidnext, (uint32_t)-1);
if (mailbox_search_deinit(&search_ctx) < 0)
ret = -1;
if (ret == 0 && array_is_created(ctx->qresync_sample_seqset))
expunges_drop_known(ctx, mail, expunges);
mail_free(&mail);
(void)mailbox_transaction_commit(&trans);
return ret;
}
static int
imap_fetch_send_vanished(struct imap_fetch_context *ctx)
{
const struct mail_search_arg *uidarg = ctx->search_args->args;
const struct mail_search_arg *modseqarg = uidarg->next;
const ARRAY_TYPE(seq_range) *uids = &uidarg->value.seqset;
uint64_t modseq = modseqarg->value.modseq->modseq;
ARRAY_TYPE(seq_range) expunges;
string_t *str;
int ret = 0;
i_array_init(&expunges, array_count(uids));
if (!mailbox_get_expunged_uids(ctx->box, modseq, uids, &expunges)) {
/* return all expunged UIDs */
if (get_expunges_fallback(ctx, uids, &expunges) < 0) {
array_clear(&expunges);
ret = -1;
}
}
if (array_count(&expunges) > 0) {
str = str_new(default_pool, 128);
str_append(str, "* VANISHED (EARLIER) ");
imap_write_seq_range(str, &expunges);
str_append(str, "\r\n");
o_stream_send(ctx->client->output, str_data(str), str_len(str));
str_free(&str);
}
array_free(&expunges);
return ret;
}
int imap_fetch_begin(struct imap_fetch_context *ctx)
{
const void *null = NULL;
const void *data;
if (ctx->send_vanished) {
if (imap_fetch_send_vanished(ctx) < 0) {
ctx->failed = TRUE;
return -1;
}
}
if (ctx->flags_update_seen) {
if (mailbox_is_readonly(ctx->box))
ctx->flags_update_seen = FALSE;
else if (!ctx->flags_have_handler) {
ctx->flags_show_only_seen_changes = TRUE;
(void)imap_fetch_init_handler(ctx, "FLAGS", NULL);
}
}
if (buffer_get_used_size(ctx->all_headers_buf) != 0 &&
((ctx->fetch_data & (MAIL_FETCH_STREAM_HEADER |
MAIL_FETCH_STREAM_BODY)) == 0)) {
buffer_append(ctx->all_headers_buf, &null, sizeof(null));
data = buffer_get_data(ctx->all_headers_buf, NULL);
ctx->all_headers_ctx =
mailbox_header_lookup_init(ctx->box, data);
}
if ((ctx->fetch_data &
(MAIL_FETCH_STREAM_HEADER | MAIL_FETCH_STREAM_BODY)) != 0)
ctx->fetch_data |= MAIL_FETCH_NUL_STATE;
ctx->trans = mailbox_transaction_begin(ctx->box,
MAILBOX_TRANSACTION_FLAG_HIDE);
ctx->select_counter = ctx->client->select_counter;
ctx->mail = mail_alloc(ctx->trans, ctx->fetch_data,
ctx->all_headers_ctx);
/* Delayed uidset -> seqset conversion. VANISHED needs the uidset. */
mail_search_args_init(ctx->search_args, ctx->box, TRUE,
&ctx->cmd->client->search_saved_uidset);
ctx->search_ctx =
mailbox_search_init(ctx->trans, ctx->search_args, NULL);
return 0;
}
static int imap_fetch_flush_buffer(struct imap_fetch_context *ctx)
{
const unsigned char *data;
size_t len;
data = str_data(ctx->cur_str);
len = str_len(ctx->cur_str);
if (len == 0)
return 0;
/* there's an extra space at the end if we added any fetch items
to buffer */
if (data[len-1] == ' ') {
len--;
ctx->first = FALSE;
}
if (o_stream_send(ctx->client->output, data, len) < 0)
return -1;
str_truncate(ctx->cur_str, 0);
return 0;
}
static int imap_fetch_send_nil_reply(struct imap_fetch_context *ctx)
{
const struct imap_fetch_context_handler *handler;
if (!ctx->first)
str_append_c(ctx->cur_str, ' ');
handler = array_idx(&ctx->handlers, ctx->cur_handler);
str_printfa(ctx->cur_str, "%s %s ",
handler->name, handler->nil_reply);
if (!handler->buffered) {
if (imap_fetch_flush_buffer(ctx) < 0)
return -1;
}
return 0;
}
static int imap_fetch_more_int(struct imap_fetch_context *ctx)
{
struct client *client = ctx->client;
const struct imap_fetch_context_handler *handlers;
unsigned int count;
int ret;
if (ctx->cont_handler != NULL) {
ret = ctx->cont_handler(ctx);
if (ret == 0)
return 0;
if (ret < 0) {
if (client->output->closed)
return -1;
if (ctx->cur_mail->expunged) {
/* not an error, just lost it. */
ctx->partial_fetch = TRUE;
if (imap_fetch_send_nil_reply(ctx) < 0)
return -1;
} else {
return -1;
}
}
ctx->cont_handler = NULL;
ctx->cur_offset = 0;
ctx->cur_handler++;
if (ctx->cur_input != NULL)
i_stream_unref(&ctx->cur_input);
}
handlers = array_get(&ctx->handlers, &count);
for (;;) {
if (o_stream_get_buffer_used_size(client->output) >=
CLIENT_OUTPUT_OPTIMAL_SIZE) {
ret = o_stream_flush(client->output);
if (ret <= 0)
return ret;
}
if (ctx->cur_mail == NULL) {
if (ctx->cmd->cancel)
return 1;
if (mailbox_search_next(ctx->search_ctx,
ctx->mail) <= 0)
break;
ctx->cur_mail = ctx->mail;
str_printfa(ctx->cur_str, "* %u FETCH (",
ctx->cur_mail->seq);
ctx->first = TRUE;
ctx->line_finished = FALSE;
}
for (; ctx->cur_handler < count; ctx->cur_handler++) {
if (str_len(ctx->cur_str) > 0 &&
!handlers[ctx->cur_handler].buffered) {
/* first non-buffered handler.
flush the buffer. */
ctx->line_partial = TRUE;
if (imap_fetch_flush_buffer(ctx) < 0)
return -1;
}
i_assert(ctx->cur_input == NULL);
T_BEGIN {
const struct imap_fetch_context_handler *h =
&handlers[ctx->cur_handler];
ret = h->handler(ctx, ctx->cur_mail,
h->context);
} T_END;
if (ret == 0)
return 0;
if (ret < 0) {
if (ctx->cur_mail->expunged) {
/* not an error, just lost it. */
ctx->partial_fetch = TRUE;
if (imap_fetch_send_nil_reply(ctx) < 0)
return -1;
} else {
i_assert(ret < 0 ||
ctx->cont_handler != NULL);
return -1;
}
}
ctx->cont_handler = NULL;
ctx->cur_offset = 0;
if (ctx->cur_input != NULL)
i_stream_unref(&ctx->cur_input);
}
if (str_len(ctx->cur_str) > 0) {
/* no non-buffered handlers */
if (imap_fetch_flush_buffer(ctx) < 0)
return -1;
}
ctx->line_finished = TRUE;
ctx->line_partial = FALSE;
if (o_stream_send(client->output, ")\r\n", 3) < 0)
return -1;
client->last_output = ioloop_time;
ctx->cur_mail = NULL;
ctx->cur_handler = 0;
}
return 1;
}
int imap_fetch_more(struct imap_fetch_context *ctx)
{
int ret;
i_assert(ctx->client->output_lock == NULL ||
ctx->client->output_lock == ctx->cmd);
ret = imap_fetch_more_int(ctx);
if (ret < 0)
ctx->failed = TRUE;
if (ctx->line_partial) {
/* nothing can be sent until FETCH is finished */
ctx->client->output_lock = ctx->cmd;
}
return ret;
}
int imap_fetch_deinit(struct imap_fetch_context *ctx)
{
const struct imap_fetch_context_handler *handlers;
unsigned int i, count;
handlers = array_get(&ctx->handlers, &count);
for (i = 0; i < count; i++) {
if (handlers[i].want_deinit)
handlers[i].handler(ctx, NULL, handlers[i].context);
}
if (!ctx->line_finished) {
if (imap_fetch_flush_buffer(ctx) < 0)
ctx->failed = TRUE;
if (o_stream_send(ctx->client->output, ")\r\n", 3) < 0)
ctx->failed = TRUE;
}
str_free(&ctx->cur_str);
if (ctx->cur_input != NULL)
i_stream_unref(&ctx->cur_input);
if (ctx->mail != NULL)
mail_free(&ctx->mail);
if (ctx->search_ctx != NULL) {
if (mailbox_search_deinit(&ctx->search_ctx) < 0)
ctx->failed = TRUE;
}
if (ctx->all_headers_ctx != NULL)
mailbox_header_lookup_deinit(&ctx->all_headers_ctx);
if (ctx->trans != NULL) {
/* even if something failed, we want to commit changes to
cache, as well as possible \Seen flag changes for FETCH
replies we returned so far. */
if (mailbox_transaction_commit(&ctx->trans) < 0)
ctx->failed = TRUE;
}
return ctx->failed ? -1 : 0;
}
static int fetch_body(struct imap_fetch_context *ctx, struct mail *mail,
void *context ATTR_UNUSED)
{
const char *body;
if (mail_get_special(mail, MAIL_FETCH_IMAP_BODY, &body) < 0)
return -1;
if (ctx->first)
ctx->first = FALSE;
else {
if (o_stream_send(ctx->client->output, " ", 1) < 0)
return -1;
}
if (o_stream_send(ctx->client->output, "BODY (", 6) < 0 ||
o_stream_send_str(ctx->client->output, body) < 0 ||
o_stream_send(ctx->client->output, ")", 1) < 0)
return -1;
return 1;
}
static bool fetch_body_init(struct imap_fetch_context *ctx, const char *name,
const struct imap_arg **args)
{
if (name[4] == '\0') {
ctx->fetch_data |= MAIL_FETCH_IMAP_BODY;
imap_fetch_add_handler(ctx, FALSE, FALSE, name,
"("BODY_NIL_REPLY")", fetch_body, NULL);
return TRUE;
}
return fetch_body_section_init(ctx, name, args);
}
static int fetch_bodystructure(struct imap_fetch_context *ctx,
struct mail *mail, void *context ATTR_UNUSED)
{
const char *bodystructure;
if (mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE,
&bodystructure) < 0)
return -1;
if (ctx->first)
ctx->first = FALSE;
else {
if (o_stream_send(ctx->client->output, " ", 1) < 0)
return -1;
}
if (o_stream_send(ctx->client->output, "BODYSTRUCTURE (", 15) < 0 ||
o_stream_send_str(ctx->client->output, bodystructure) < 0 ||
o_stream_send(ctx->client->output, ")", 1) < 0)
return -1;
return 1;
}
static bool
fetch_bodystructure_init(struct imap_fetch_context *ctx, const char *name,
const struct imap_arg **args ATTR_UNUSED)
{
ctx->fetch_data |= MAIL_FETCH_IMAP_BODYSTRUCTURE;
imap_fetch_add_handler(ctx, FALSE, FALSE, name,
"("BODY_NIL_REPLY" NIL NIL NIL NIL)",
fetch_bodystructure, NULL);
return TRUE;
}
static int fetch_envelope(struct imap_fetch_context *ctx, struct mail *mail,
void *context ATTR_UNUSED)
{
const char *envelope;
if (mail_get_special(mail, MAIL_FETCH_IMAP_ENVELOPE, &envelope) < 0)
return -1;
if (ctx->first)
ctx->first = FALSE;
else {
if (o_stream_send(ctx->client->output, " ", 1) < 0)
return -1;
}
if (o_stream_send(ctx->client->output, "ENVELOPE (", 10) < 0 ||
o_stream_send_str(ctx->client->output, envelope) < 0 ||
o_stream_send(ctx->client->output, ")", 1) < 0)
return -1;
return 1;
}
static bool
fetch_envelope_init(struct imap_fetch_context *ctx, const char *name,
const struct imap_arg **args ATTR_UNUSED)
{
ctx->fetch_data |= MAIL_FETCH_IMAP_ENVELOPE;
imap_fetch_add_handler(ctx, FALSE, FALSE, name, ENVELOPE_NIL_REPLY,
fetch_envelope, NULL);
return TRUE;
}
static int fetch_flags(struct imap_fetch_context *ctx, struct mail *mail,
void *context ATTR_UNUSED)
{
enum mail_flags flags;
const char *const *keywords;
flags = mail_get_flags(mail);
if (ctx->flags_update_seen && (flags & MAIL_SEEN) == 0) {
/* Add \Seen flag */
ctx->seen_flags_changed = TRUE;
flags |= MAIL_SEEN;
mail_update_flags(mail, MODIFY_ADD, MAIL_SEEN);
} else if (ctx->flags_show_only_seen_changes) {
return 1;
}
keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
mail_get_keyword_indexes(mail));
str_append(ctx->cur_str, "FLAGS (");
imap_write_flags(ctx->cur_str, flags, keywords);
str_append(ctx->cur_str, ") ");
return 1;
}
static bool
fetch_flags_init(struct imap_fetch_context *ctx, const char *name,
const struct imap_arg **args ATTR_UNUSED)
{
ctx->flags_have_handler = TRUE;
ctx->fetch_data |= MAIL_FETCH_FLAGS;
imap_fetch_add_handler(ctx, TRUE, FALSE, name, "()", fetch_flags, NULL);
return TRUE;
}
static int fetch_internaldate(struct imap_fetch_context *ctx, struct mail *mail,
void *context ATTR_UNUSED)
{
time_t date;
if (mail_get_received_date(mail, &date) < 0)
return -1;
str_printfa(ctx->cur_str, "INTERNALDATE \"%s\" ",
imap_to_datetime(date));
return 1;
}
static bool
fetch_internaldate_init(struct imap_fetch_context *ctx, const char *name,
const struct imap_arg **args ATTR_UNUSED)
{
ctx->fetch_data |= MAIL_FETCH_RECEIVED_DATE;
imap_fetch_add_handler(ctx, TRUE, FALSE, name,
"\"01-Jan-1970 00:00:00 +0000\"",
fetch_internaldate, NULL);
return TRUE;
}
static int fetch_modseq(struct imap_fetch_context *ctx, struct mail *mail,
void *context ATTR_UNUSED)
{
str_printfa(ctx->cur_str, "MODSEQ %llu ",
(unsigned long long)mail_get_modseq(mail));
return 1;
}
static bool
fetch_modseq_init(struct imap_fetch_context *ctx, const char *name,
const struct imap_arg **args ATTR_UNUSED)
{
client_enable(ctx->client, MAILBOX_FEATURE_CONDSTORE);
imap_fetch_add_handler(ctx, TRUE, FALSE, name, NULL,
fetch_modseq, NULL);
return TRUE;
}
static int fetch_uid(struct imap_fetch_context *ctx, struct mail *mail,
void *context ATTR_UNUSED)
{
str_printfa(ctx->cur_str, "UID %u ", mail->uid);
return 1;
}
static bool
fetch_uid_init(struct imap_fetch_context *ctx ATTR_UNUSED, const char *name,
const struct imap_arg **args ATTR_UNUSED)
{
imap_fetch_add_handler(ctx, TRUE, FALSE, name, NULL, fetch_uid, NULL);
return TRUE;
}
const struct imap_fetch_handler default_handlers[8] = {
{ "BODY", fetch_body_init },
{ "BODYSTRUCTURE", fetch_bodystructure_init },
{ "ENVELOPE", fetch_envelope_init },
{ "FLAGS", fetch_flags_init },
{ "INTERNALDATE", fetch_internaldate_init },
{ "MODSEQ", fetch_modseq_init },
{ "RFC822", fetch_rfc822_init },
{ "UID", fetch_uid_init }
};