Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions src/imap/imap-fetch-body.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ static const char *get_body_name(const struct imap_fetch_body_data *body)
return str_c(str);
}

static string_t *get_prefix(struct imap_fetch_state *state,
static string_t *get_prefix(struct imap_fetch_context *ctx,
const struct imap_fetch_body_data *body,
uoff_t size, bool has_nuls)
{
struct imap_fetch_state *state = &ctx->state;
string_t *str;

str = t_str_new(128);
Expand All @@ -93,7 +94,26 @@ static string_t *get_prefix(struct imap_fetch_state *state,
str_append(str, " NIL");
else if (has_nuls && body->binary)
str_printfa(str, " ~{%"PRIuUOFF_T"}\r\n", size);
else
else if (size == 0 &&
(ctx->client->set->parsed_workarounds &
WORKAROUND_EMPTY_PARTIAL_AS_QUOTED) != 0) {
/* An empty string, as RFC 9051 6.4.5 requires for a range
past the end of a section. "" and {0} are the same value
(string = quoted / literal), but only the literal splits
the response across two lines.

swift-nio-imap's FramingParser mishandles the zero-length
case: it emits the header as a complete frame, enters
.insideLiteral(remaining: 0), leaves it without consuming
a byte, then returns .incomplete because frameLength is
still 0. Only new network bytes re-drive it, and after a
finished FETCH none arrive -- iOS Mail hangs ~90 s.

Every zero-length literal is affected, not just partial
fetches. imap_append_nstring() already prefers "" for an
empty string; this was the one place that did not. */
str_append(str, " \"\"");
} else
str_printfa(str, " {%"PRIuUOFF_T"}\r\n", size);
return str;
}
Expand Down Expand Up @@ -201,7 +221,7 @@ static int fetch_body_msgpart(struct imap_fetch_context *ctx, struct mail *mail,
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,
str = get_prefix(ctx, body, ctx->state.cur_size,
result.binary_decoded_input_has_nuls);
o_stream_nsend(ctx->client->output, str_data(str), str_len(str));

Expand Down
1 change: 1 addition & 0 deletions src/imap/imap-settings.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ static const struct imap_client_workaround_list imap_client_workaround_list[] =
{ "delay-newmail", WORKAROUND_DELAY_NEWMAIL },
{ "tb-extra-mailbox-sep", WORKAROUND_TB_EXTRA_MAILBOX_SEP },
{ "tb-lsub-flags", WORKAROUND_TB_LSUB_FLAGS },
{ "empty-partial-as-quoted", WORKAROUND_EMPTY_PARTIAL_AS_QUOTED },
{ NULL, 0 }
};

Expand Down
3 changes: 2 additions & 1 deletion src/imap/imap-settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ struct mail_user_settings;
enum imap_client_workarounds {
WORKAROUND_DELAY_NEWMAIL = 0x01,
WORKAROUND_TB_EXTRA_MAILBOX_SEP = 0x08,
WORKAROUND_TB_LSUB_FLAGS = 0x10
WORKAROUND_TB_LSUB_FLAGS = 0x10,
WORKAROUND_EMPTY_PARTIAL_AS_QUOTED = 0x20
};

enum imap_client_fetch_failure {
Expand Down