summaryrefslogtreecommitdiffstats
path: root/src/imap/imap-fetch-body.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/imap-fetch-body.c')
-rw-r--r--src/imap/imap-fetch-body.c722
1 files changed, 722 insertions, 0 deletions
diff --git a/src/imap/imap-fetch-body.c b/src/imap/imap-fetch-body.c
new file mode 100644
index 0000000..b467334
--- /dev/null
+++ b/src/imap/imap-fetch-body.c
@@ -0,0 +1,722 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "buffer.h"
+#include "str.h"
+#include "strescape.h"
+#include "istream.h"
+#include "ostream.h"
+#include "istream-header-filter.h"
+#include "message-parser.h"
+#include "mail-storage-private.h"
+#include "imap-quote.h"
+#include "imap-parser.h"
+#include "imap-msgpart.h"
+#include "imap-fetch.h"
+
+#include <ctype.h>
+#include <unistd.h>
+
+struct imap_fetch_body_data {
+ const char *section; /* NOTE: always uppercased */
+ struct imap_msgpart *msgpart;
+
+ bool partial:1;
+ bool binary:1;
+ bool binary_size:1;
+};
+
+struct imap_fetch_preview_data {
+ /* If TRUE, lazy modifier is specified. */
+ bool lazy:1;
+ /* Uses the pre-RFC 8970 standard (requires algorithm to be returned
+ * as part of FETCH response). */
+ bool old_standard:1;
+ /* If TRUE, uses "PREVIEW" command; if FALSE, uses older "SNIPPET"
+ * command. */
+ bool preview_cmd:1;
+};
+
+static void fetch_read_error(struct imap_fetch_context *ctx,
+ const char **disconnect_reason_r)
+{
+ struct imap_fetch_state *state = &ctx->state;
+
+ if (state->cur_input->stream_errno == ENOENT) {
+ if (state->cur_mail->expunged) {
+ *disconnect_reason_r = "Mail expunged while it was being FETCHed";
+ return;
+ }
+ }
+ mail_set_critical(state->cur_mail,
+ "read(%s) failed: %s (FETCH %s)",
+ i_stream_get_name(state->cur_input),
+ i_stream_get_error(state->cur_input),
+ state->cur_human_name);
+ *disconnect_reason_r = "FETCH read() failed";
+}
+
+static const char *get_body_name(const struct imap_fetch_body_data *body)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ if (body->binary_size)
+ str_append(str, "BINARY.SIZE");
+ else if (body->binary)
+ str_append(str, "BINARY");
+ else
+ str_append(str, "BODY");
+ str_printfa(str, "[%s]", body->section);
+ if (body->partial) {
+ str_printfa(str, "<%"PRIuUOFF_T">",
+ imap_msgpart_get_partial_offset(body->msgpart));
+ }
+ return str_c(str);
+}
+
+static string_t *get_prefix(struct imap_fetch_state *state,
+ const struct imap_fetch_body_data *body,
+ uoff_t size, bool has_nuls)
+{
+ string_t *str;
+
+ str = t_str_new(128);
+ if (state->cur_first)
+ state->cur_first = FALSE;
+ else
+ str_append_c(str, ' ');
+
+ str_append(str, get_body_name(body));
+
+ if (size == UOFF_T_MAX)
+ str_append(str, " NIL");
+ else if (has_nuls && body->binary)
+ str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", size);
+ else
+ str_printfa(str, " {%"PRIuUOFF_T"}\r\n", size);
+ return str;
+}
+
+static int fetch_stream_continue(struct imap_fetch_context *ctx)
+{
+ struct imap_fetch_state *state = &ctx->state;
+ const char *disconnect_reason;
+ uoff_t orig_input_offset = state->cur_input->v_offset;
+ enum ostream_send_istream_result res;
+
+ o_stream_set_max_buffer_size(ctx->client->output, 0);
+ res = o_stream_send_istream(ctx->client->output, state->cur_input);
+ o_stream_set_max_buffer_size(ctx->client->output, SIZE_MAX);
+
+ if (ctx->state.cur_stats_sizep != NULL) {
+ *ctx->state.cur_stats_sizep +=
+ state->cur_input->v_offset - orig_input_offset;
+ }
+
+ switch (res) {
+ case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
+ if (state->cur_input->v_offset != state->cur_size) {
+ /* Input stream gave less data than expected */
+ mail_set_cache_corrupted(state->cur_mail,
+ state->cur_size_field, t_strdup_printf(
+ "read(%s): FETCH %s got too little data: "
+ "%"PRIuUOFF_T" vs %"PRIuUOFF_T,
+ i_stream_get_name(state->cur_input),
+ state->cur_human_name,
+ state->cur_input->v_offset, state->cur_size));
+ client_disconnect(ctx->client, "FETCH failed");
+ return -1;
+ }
+ return 1;
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
+ i_unreached();
+ case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
+ return 0;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
+ fetch_read_error(ctx, &disconnect_reason);
+ client_disconnect(ctx->client, disconnect_reason);
+ return -1;
+ case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
+ /* client disconnected */
+ return -1;
+ }
+ i_unreached();
+}
+
+static const char *
+get_body_human_name(pool_t pool, struct imap_fetch_body_data *body)
+{
+ string_t *str;
+ uoff_t partial_offset, partial_size;
+
+ str = t_str_new(64);
+ if (body->binary)
+ str_append(str, "BINARY[");
+ else
+ str_append(str, "BODY[");
+ str_append(str, body->section);
+ str_append_c(str, ']');
+
+ partial_offset = imap_msgpart_get_partial_offset(body->msgpart);
+ partial_size = imap_msgpart_get_partial_size(body->msgpart);
+ if (partial_offset != 0 || partial_size != UOFF_T_MAX) {
+ str_printfa(str, "<%"PRIuUOFF_T, partial_offset);
+ if (partial_size != UOFF_T_MAX)
+ str_printfa(str, ".%"PRIuUOFF_T, partial_size);
+ str_append_c(str, '>');
+ }
+ return p_strdup(pool, str_c(str));
+}
+
+static void fetch_state_update_stats(struct imap_fetch_context *ctx,
+ const struct imap_msgpart *msgpart)
+{
+ if (!imap_msgpart_contains_body(msgpart)) {
+ ctx->client->fetch_hdr_count++;
+ ctx->state.cur_stats_sizep = &ctx->client->fetch_hdr_bytes;
+ } else {
+ ctx->client->fetch_body_count++;
+ ctx->state.cur_stats_sizep = &ctx->client->fetch_body_bytes;
+ }
+}
+
+static int fetch_body_msgpart(struct imap_fetch_context *ctx, struct mail *mail,
+ struct imap_fetch_body_data *body)
+{
+ struct imap_msgpart_open_result result;
+ string_t *str;
+
+ if (mail == NULL) {
+ imap_msgpart_free(&body->msgpart);
+ return 1;
+ }
+
+ if (imap_msgpart_open(mail, body->msgpart, &result) < 0)
+ return -1;
+ i_assert(result.input->v_offset == 0);
+ ctx->state.cur_input = result.input;
+ ctx->state.cur_size = result.size;
+ ctx->state.cur_size_field = result.size_field;
+ ctx->state.cur_human_name = get_body_human_name(ctx->ctx_pool, body);
+
+ fetch_state_update_stats(ctx, body->msgpart);
+ str = get_prefix(&ctx->state, body, ctx->state.cur_size,
+ result.binary_decoded_input_has_nuls);
+ o_stream_nsend(ctx->client->output, str_data(str), str_len(str));
+
+ ctx->state.cont_handler = fetch_stream_continue;
+ return ctx->state.cont_handler(ctx);
+}
+
+static int fetch_binary_size(struct imap_fetch_context *ctx, struct mail *mail,
+ struct imap_fetch_body_data *body)
+{
+ string_t *str;
+ uoff_t size;
+
+ if (mail == NULL) {
+ imap_msgpart_free(&body->msgpart);
+ return 1;
+ }
+
+ if (imap_msgpart_size(mail, body->msgpart, &size) < 0)
+ return -1;
+
+ str = t_str_new(128);
+ if (ctx->state.cur_first)
+ ctx->state.cur_first = FALSE;
+ else
+ str_append_c(str, ' ');
+ str_printfa(str, "%s %"PRIuUOFF_T, get_body_name(body), size);
+
+ if (o_stream_send(ctx->client->output, str_data(str), str_len(str)) < 0)
+ return -1;
+ return 1;
+}
+
+/* Parse next digits in string into integer. Returns -1 if the integer
+ becomes too big and wraps. */
+static int read_uoff_t(const char **p, uoff_t *value)
+{
+ return str_parse_uoff(*p, value, p);
+}
+
+static int
+body_header_fields_parse(struct imap_fetch_init_context *ctx,
+ struct imap_fetch_body_data *body, const char *prefix,
+ const struct imap_arg *args, unsigned int args_count)
+{
+ string_t *str;
+ const char *value;
+ size_t i;
+
+ ctx->fetch_ctx->fetch_header_fields = TRUE;
+
+ str = str_new(ctx->pool, 128);
+ str_append(str, prefix);
+ str_append(str, " (");
+
+ for (i = 0; i < args_count; i++) {
+ if (!imap_arg_get_astring(&args[i], &value)) {
+ ctx->error = "Invalid BODY[..] parameter: "
+ "Header list contains non-strings";
+ return -1;
+ }
+ value = t_str_ucase(value);
+
+ if (i != 0)
+ str_append_c(str, ' ');
+
+ if (args[i].type == IMAP_ARG_ATOM)
+ str_append(str, value);
+ else
+ imap_append_quoted(str, value);
+ }
+ str_append_c(str, ')');
+ body->section = str_c(str);
+ return 0;
+}
+
+static int body_parse_partial(struct imap_fetch_body_data *body,
+ const char *p, const char **error_r)
+{
+ uoff_t offset, size = UOFF_T_MAX;
+
+ if (*p == '\0')
+ return 0;
+ /* <start.end> */
+ if (*p != '<') {
+ *error_r = "Unexpected data after ']'";
+ return -1;
+ }
+
+ p++;
+ body->partial = TRUE;
+
+ if (read_uoff_t(&p, &offset) < 0 || offset > OFF_T_MAX) {
+ /* wrapped */
+ *error_r = "Too big partial start";
+ return -1;
+ }
+
+ if (*p == '.') {
+ p++;
+ if (read_uoff_t(&p, &size) < 0 || size > OFF_T_MAX) {
+ /* wrapped */
+ *error_r = "Too big partial end";
+ return -1;
+ }
+ }
+
+ if (*p != '>') {
+ *error_r = "Missing '>' in partial";
+ return -1;
+ }
+ if (p[1] != '\0') {
+ *error_r = "Unexpected data after '>'";
+ return -1;
+ }
+ imap_msgpart_set_partial(body->msgpart, offset, size);
+ return 0;
+}
+
+bool imap_fetch_body_section_init(struct imap_fetch_init_context *ctx)
+{
+ struct imap_fetch_body_data *body;
+ const struct imap_arg *list_args;
+ unsigned int list_count;
+ const char *str, *p, *error;
+
+ i_assert(str_begins(ctx->name, "BODY"));
+ p = ctx->name + 4;
+
+ body = p_new(ctx->pool, struct imap_fetch_body_data, 1);
+
+ if (str_begins(p, ".PEEK"))
+ p += 5;
+ else
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ if (*p != '[') {
+ ctx->error = "Invalid BODY[..] parameter: Missing '['";
+ return FALSE;
+ }
+
+ if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
+ /* BODY[HEADER.FIELDS.. (headers list)] */
+ if (!imap_arg_get_atom(&ctx->args[1], &str) ||
+ str[0] != ']') {
+ ctx->error = "Invalid BODY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ if (body_header_fields_parse(ctx, body, p+1,
+ list_args, list_count) < 0)
+ return FALSE;
+ p = str+1;
+ ctx->args += 2;
+ } else {
+ /* no headers list */
+ body->section = p+1;
+ p = strchr(body->section, ']');
+ if (p == NULL) {
+ ctx->error = "Invalid BODY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ body->section = p_strdup_until(ctx->pool, body->section, p);
+ p++;
+ }
+ if (imap_msgpart_parse(body->section, &body->msgpart) < 0) {
+ ctx->error = "Invalid BODY[..] section";
+ return FALSE;
+ }
+ ctx->fetch_ctx->fetch_data |=
+ imap_msgpart_get_fetch_data(body->msgpart);
+ imap_msgpart_get_wanted_headers(body->msgpart, &ctx->fetch_ctx->all_headers);
+
+ if (body_parse_partial(body, p, &error) < 0) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "Invalid BODY[..] parameter: %s", error);
+ return FALSE;
+ }
+
+ /* update the section name for the imap_fetch_add_handler() */
+ ctx->name = p_strdup(ctx->pool, get_body_name(body));
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
+ "NIL", fetch_body_msgpart, body);
+ return TRUE;
+}
+
+bool imap_fetch_binary_init(struct imap_fetch_init_context *ctx)
+{
+ struct imap_fetch_body_data *body;
+ const struct imap_arg *list_args;
+ unsigned int list_count;
+ const char *str, *p, *error;
+
+ i_assert(str_begins(ctx->name, "BINARY"));
+ p = ctx->name + 6;
+
+ body = p_new(ctx->pool, struct imap_fetch_body_data, 1);
+ body->binary = TRUE;
+
+ if (str_begins(p, ".SIZE")) {
+ /* fetch decoded size of the section */
+ p += 5;
+ body->binary_size = TRUE;
+ } else if (str_begins(p, ".PEEK")) {
+ p += 5;
+ } else {
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ }
+ if (*p != '[') {
+ ctx->error = "Invalid BINARY[..] parameter: Missing '['";
+ return FALSE;
+ }
+
+ if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
+ /* BINARY[HEADER.FIELDS.. (headers list)] */
+ if (!imap_arg_get_atom(&ctx->args[1], &str) ||
+ str[0] != ']') {
+ ctx->error = "Invalid BINARY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ if (body_header_fields_parse(ctx, body, p+1,
+ list_args, list_count) < 0)
+ return FALSE;
+ p = str+1;
+ ctx->args += 2;
+ } else {
+ /* no headers list */
+ body->section = p+1;
+ p = strchr(body->section, ']');
+ if (p == NULL) {
+ ctx->error = "Invalid BINARY[..] parameter: Missing ']'";
+ return FALSE;
+ }
+ body->section = p_strdup_until(ctx->pool, body->section, p);
+ p++;
+ }
+ if (imap_msgpart_parse(body->section, &body->msgpart) < 0) {
+ ctx->error = "Invalid BINARY[..] section";
+ return FALSE;
+ }
+ imap_msgpart_set_decode_to_binary(body->msgpart);
+ ctx->fetch_ctx->fetch_data |=
+ imap_msgpart_get_fetch_data(body->msgpart);
+
+ if (!body->binary_size) {
+ if (body_parse_partial(body, p, &error) < 0) {
+ ctx->error = p_strdup_printf(ctx->pool,
+ "Invalid BINARY[..] parameter: %s", error);
+ return FALSE;
+ }
+ }
+
+ /* update the section name for the imap_fetch_add_handler() */
+ ctx->name = p_strdup(ctx->pool, get_body_name(body));
+ if (body->binary_size) {
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
+ "0", fetch_binary_size, body);
+ } else {
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
+ "NIL", fetch_body_msgpart, body);
+ }
+ return TRUE;
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822_size(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ uoff_t size;
+
+ if (mail_get_virtual_size(mail, &size) < 0)
+ return -1;
+
+ str_printfa(ctx->state.cur_str, "RFC822.SIZE %"PRIuUOFF_T" ", size);
+ return 1;
+}
+
+static int
+fetch_and_free_msgpart(struct imap_fetch_context *ctx,
+ struct mail *mail, struct imap_msgpart **_msgpart)
+{
+ struct imap_msgpart_open_result result;
+ int ret;
+
+ ret = imap_msgpart_open(mail, *_msgpart, &result);
+ imap_msgpart_free(_msgpart);
+ if (ret < 0)
+ return -1;
+ i_assert(result.input->v_offset == 0);
+ ctx->state.cur_input = result.input;
+ ctx->state.cur_size = result.size;
+ ctx->state.cur_size_field = result.size_field;
+ ctx->state.cont_handler = fetch_stream_continue;
+ return 0;
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ struct imap_msgpart *msgpart;
+ const char *str;
+
+ msgpart = imap_msgpart_full();
+ fetch_state_update_stats(ctx, msgpart);
+ if (fetch_and_free_msgpart(ctx, mail, &msgpart) < 0)
+ return -1;
+
+ str = t_strdup_printf(" RFC822 {%"PRIuUOFF_T"}\r\n",
+ ctx->state.cur_size);
+ if (ctx->state.cur_first) {
+ str++; ctx->state.cur_first = FALSE;
+ }
+ o_stream_nsend_str(ctx->client->output, str);
+
+ ctx->state.cur_human_name = "RFC822";
+ return ctx->state.cont_handler(ctx);
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822_header(struct imap_fetch_context *ctx,
+ struct mail *mail, void *context ATTR_UNUSED)
+{
+ struct imap_msgpart *msgpart;
+ const char *str;
+
+ msgpart = imap_msgpart_header();
+ fetch_state_update_stats(ctx, msgpart);
+ if (fetch_and_free_msgpart(ctx, mail, &msgpart) < 0)
+ return -1;
+
+ str = t_strdup_printf(" RFC822.HEADER {%"PRIuUOFF_T"}\r\n",
+ ctx->state.cur_size);
+ if (ctx->state.cur_first) {
+ str++; ctx->state.cur_first = FALSE;
+ }
+ o_stream_nsend_str(ctx->client->output, str);
+
+ ctx->state.cur_human_name = "RFC822.HEADER";
+ return ctx->state.cont_handler(ctx);
+}
+
+static int ATTR_NULL(3)
+fetch_rfc822_text(struct imap_fetch_context *ctx, struct mail *mail,
+ void *context ATTR_UNUSED)
+{
+ struct imap_msgpart *msgpart;
+ const char *str;
+
+ msgpart = imap_msgpart_body();
+ fetch_state_update_stats(ctx, msgpart);
+ if (fetch_and_free_msgpart(ctx, mail, &msgpart) < 0)
+ return -1;
+
+ str = t_strdup_printf(" RFC822.TEXT {%"PRIuUOFF_T"}\r\n",
+ ctx->state.cur_size);
+ if (ctx->state.cur_first) {
+ str++; ctx->state.cur_first = FALSE;
+ }
+ o_stream_nsend_str(ctx->client->output, str);
+
+ ctx->state.cur_human_name = "RFC822.TEXT";
+ return ctx->state.cont_handler(ctx);
+}
+
+bool imap_fetch_rfc822_init(struct imap_fetch_init_context *ctx)
+{
+ const char *name = ctx->name;
+
+ if (name[6] == '\0') {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER |
+ MAIL_FETCH_STREAM_BODY;
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ imap_fetch_add_handler(ctx, 0, "NIL",
+ fetch_rfc822, NULL);
+ return TRUE;
+ }
+
+ if (strcmp(name+6, ".SIZE") == 0) {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_VIRTUAL_SIZE;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "0", fetch_rfc822_size, NULL);
+ return TRUE;
+ }
+ if (strcmp(name+6, ".HEADER") == 0) {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_HEADER;
+ imap_fetch_add_handler(ctx, 0, "NIL",
+ fetch_rfc822_header, NULL);
+ return TRUE;
+ }
+ if (strcmp(name+6, ".TEXT") == 0) {
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_STREAM_BODY;
+ ctx->fetch_ctx->flags_update_seen = TRUE;
+ imap_fetch_add_handler(ctx, 0, "NIL",
+ fetch_rfc822_text, NULL);
+ return TRUE;
+ }
+
+ ctx->error = t_strconcat("Unknown parameter ", name, NULL);
+ return FALSE;
+}
+
+static int ATTR_NULL(3)
+fetch_snippet(struct imap_fetch_context *ctx, struct mail *mail,
+ struct imap_fetch_preview_data *preview)
+{
+ enum mail_lookup_abort temp_lookup_abort = preview->lazy ? MAIL_LOOKUP_ABORT_NOT_IN_CACHE_START_CACHING : mail->lookup_abort;
+ enum mail_lookup_abort orig_lookup_abort = mail->lookup_abort;
+ const char *resp, *snippet;
+ int ret;
+
+ mail->lookup_abort = temp_lookup_abort;
+ ret = mail_get_special(mail, MAIL_FETCH_BODY_SNIPPET, &snippet);
+ mail->lookup_abort = orig_lookup_abort;
+
+ resp = preview->preview_cmd ? "PREVIEW" : "SNIPPET";
+
+ if (ret == 0) {
+ /* got it => nothing to do */
+ snippet++; /* skip over snippet version byte */
+ } else if (mailbox_get_last_mail_error(mail->box) != MAIL_ERROR_LOOKUP_ABORTED) {
+ /* actual error => bail */
+ return -1;
+ } else {
+ /*
+ * Two ways to get here:
+ * - not in cache && lazy => give up
+ * - not in cache && !lazy => someone higher up set
+ * MAIL_LOOKUP_ABORT_NOT_IN_CACHE and so even though we got
+ * a non-lazy request we failed the cache lookup.
+ *
+ * This is not an error, but since the scenario is
+ * sufficiently convoluted this else branch serves to
+ * document it.
+ *
+ * This path will return NIL as the preview response.
+ */
+ }
+
+ str_append(ctx->state.cur_str, resp);
+ if (preview->old_standard)
+ str_append(ctx->state.cur_str, " (FUZZY ");
+ else
+ str_append(ctx->state.cur_str, " ");
+ if (ret == 0)
+ imap_append_string(ctx->state.cur_str, snippet);
+ else
+ str_append(ctx->state.cur_str, "NIL");
+ if (preview->old_standard)
+ str_append(ctx->state.cur_str, ")");
+ str_append_c(ctx->state.cur_str, ' ');
+
+ return 1;
+}
+
+static bool fetch_preview_init(struct imap_fetch_init_context *ctx,
+ bool preview_cmd)
+{
+ const struct imap_arg *list_args;
+ unsigned int list_count;
+ struct imap_fetch_preview_data *preview;
+
+ preview = p_new(ctx->pool, struct imap_fetch_preview_data, 1);
+ preview->preview_cmd = preview_cmd;
+ /* We can tell we are using the "Old" (pre-RFC 8970) standard
+ * if:
+ * 1) SNIPPET command is used
+ * 2) FUZZY algorithm is explicitly requested
+ * The one usage we cannot catch is if PREVIEW command is used, and
+ * no algorithm is specified - the pre-RFC standard requires that the
+ * algorithm must be output as part of the response; the RFC standard
+ * does not output the algorithm. There is unfortunately nothing we
+ * can do about this, so we need to send the standards compliant
+ * way. */
+ preview->old_standard = !preview_cmd;
+
+ if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
+ unsigned int i;
+
+ for (i = 0; i < list_count; i++) {
+ const char *str;
+
+ if (!imap_arg_get_atom(&list_args[i], &str)) {
+ ctx->error = "Invalid PREVIEW modifier";
+ return FALSE;
+ }
+
+ if (strcasecmp(str, "LAZY") == 0) {
+ preview->lazy = TRUE;
+ } else if (strcasecmp(str, "LAZY=FUZZY") == 0) {
+ preview->lazy = TRUE;
+ preview->old_standard = TRUE;
+ } else if (strcasecmp(str, "FUZZY") == 0) {
+ preview->old_standard = TRUE;
+ } else {
+ ctx->error = t_strdup_printf("'%s' is not a "
+ "supported PREVIEW modifier",
+ str);
+ return FALSE;
+ }
+ }
+
+ ctx->args += list_count;
+ }
+
+ ctx->fetch_ctx->fetch_data |= MAIL_FETCH_BODY_SNIPPET;
+ imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_BUFFERED,
+ "NIL", fetch_snippet, preview);
+ return TRUE;
+}
+
+bool imap_fetch_preview_init(struct imap_fetch_init_context *ctx)
+{
+ return fetch_preview_init(ctx, TRUE);
+}
+
+bool imap_fetch_snippet_init(struct imap_fetch_init_context *ctx)
+{
+ return fetch_preview_init(ctx, FALSE);
+}