Skip to content

Commit f4f3b56

Browse files
committed
imap.c: Use longer libetpan timeout for STATUS and LIST.
Port the LBBS fix for STATUS/LIST libetpan timeouts from: InterLinked1/lbbs@e842676 We do not need to port this LBBS fix for MOVE/COPY libetpan timeouts, since MOVE/COPY in evergreen only handle one message at a time: InterLinked1/lbbs@22f1615 Simplify parsing of STATUS response using ported LBBS optimization from: InterLinked1/lbbs@ce62d96
1 parent e983286 commit f4f3b56

File tree

1 file changed

+48
-35
lines changed

1 file changed

+48
-35
lines changed

imap.c

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -283,44 +283,51 @@ static void parse_status(struct client *client, struct mailbox *mbox, char *rest
283283
return;
284284
}
285285

286-
str = strstr(tmp, "MESSAGES ");
287-
if (str) {
288-
str += STRLEN("MESSAGES ");
289-
if (!strlen_zero(str)) {
290-
mbox->total = (uint32_t) atol(str);
291-
}
292-
} else if (expect_full) {
293-
client_warning("Failed to parse MESSAGES");
286+
/* Helper macro from LBBS mod_webmail.c */
287+
/* Note: if var, *var = num doesn't work, because that would could be if NULL, *NULL = num.
288+
* Since only messages requires the var argument, that case is just hardcoded to compile.
289+
* The other checks should boil down to if (NULL) and be optimized out by the compiler. */
290+
#define PARSE_STATUS_ITEM(item, respitem) \
291+
str = strstr(tmp, item " "); \
292+
if (str) { \
293+
str += STRLEN(item " "); \
294+
if (!strlen_zero(str)) { \
295+
mbox->respitem = (uint32_t) atol(str); \
296+
} \
297+
} else if (expect_full) { \
298+
client_warning("Failed to parse " item ""); \
294299
}
295-
str = strstr(tmp, "RECENT ");
296-
if (str) {
297-
str += STRLEN("RECENT ");
298-
if (!strlen_zero(str)) {
299-
mbox->recent = (uint32_t) atol(str);
300-
}
301-
} else if (expect_full) {
302-
client_warning("Failed to parse RECENT");
300+
301+
PARSE_STATUS_ITEM("MESSAGES", total);
302+
PARSE_STATUS_ITEM("RECENT", recent);
303+
PARSE_STATUS_ITEM("UNSEEN", unseen);
304+
if (IMAP_HAS_CAPABILITY(client, IMAP_CAPABILITY_STATUS_SIZE)) {
305+
PARSE_STATUS_ITEM("SIZE", size);
303306
}
304-
str = strstr(tmp, "UNSEEN ");
305-
if (str) {
306-
str += STRLEN("UNSEEN ");
307-
if (!strlen_zero(str)) {
308-
mbox->unseen = (uint32_t) atol(str);
309-
}
310-
} else if (expect_full) {
311-
client_warning("Failed to parse UNSEEN");
307+
#undef PARSE_STATUS_ITEM
308+
}
309+
310+
/* libetpan timeout handling, from LBBS mod_webmail.c */
311+
#define COMMAND_READ_LARGE_TIMEOUT 45
312+
313+
/*!
314+
* \brief Set the mailstream_low timeout, used to determine how long to wait for another line of the response.
315+
* \note By default, this timeout appears to be about 15 seconds, but for some commands (e.g. SIZE/LIST), this can be too short.
316+
* \param client
317+
* \param timeout Timeout, in seconds
318+
* \param[out] If provided, old timeout will be stored here.
319+
*/
320+
static void set_command_read_timeout(struct client *client, time_t timeout, time_t *restrict old_timeout)
321+
{
322+
time_t current_timeout = mailstream_low_get_timeout(mailstream_get_low(client->imap->imap_stream));
323+
if (old_timeout) {
324+
*old_timeout = current_timeout;
312325
}
313-
if (IMAP_HAS_CAPABILITY(client, IMAP_CAPABILITY_STATUS_SIZE)) {
314-
str = strstr(tmp, "SIZE ");
315-
if (str) {
316-
str += STRLEN("SIZE ");
317-
if (!strlen_zero(str)) {
318-
mbox->size = (size_t) atol(str);
319-
}
320-
} else if (expect_full) {
321-
client_warning("Failed to parse SIZE");
322-
}
326+
if (current_timeout == timeout) {
327+
return; /* No change */
323328
}
329+
client_debug(4, "Setting libetpan timeout to %ld", timeout);
330+
return mailstream_low_set_timeout(mailstream_get_low(client->imap->imap_stream), timeout);
324331
}
325332

326333
int client_status_command(struct client *client, struct mailbox *mbox, char *restrict list_status_resp)
@@ -329,6 +336,7 @@ int client_status_command(struct client *client, struct mailbox *mbox, char *res
329336
struct mailimap_status_att_list *att_list;
330337
struct mailimap_mailbox_data_status *status;
331338
clistiter *cur;
339+
time_t old_timeout;
332340

333341
/* If LIST-STATUS is supported, we might not need to make a STATUS request at all */
334342
if (list_status_resp) {
@@ -374,7 +382,9 @@ int client_status_command(struct client *client, struct mailbox *mbox, char *res
374382
log_mailimap_error(client, res, "Failed to construct STATUS");
375383
goto cleanup;
376384
}
385+
set_command_read_timeout(client, COMMAND_READ_LARGE_TIMEOUT, &old_timeout);
377386
res = mailimap_status(client->imap, mbox->name, att_list, &status);
387+
set_command_read_timeout(client, old_timeout, NULL);
378388

379389
if (res != MAILIMAP_NO_ERROR) {
380390
log_mailimap_error(client, res, "STATUS failed");
@@ -962,6 +972,7 @@ static int __client_list(struct client *client)
962972
/* This is a single-threaded application, so there is no concurrency risk to making this static/global,
963973
* and it's probably better to put such a large buffer in the global segment rather than on the stack. */
964974
static char list_status_buf[32768]; /* Hopefully big enough to fit the entire LIST-STATUS response */
975+
time_t old_timeout;
965976

966977
#ifdef TEST_MODE
967978
if (test_cmp(client)) {
@@ -971,6 +982,7 @@ static int __client_list(struct client *client)
971982

972983
client_status("Querying mailbox list");
973984

985+
set_command_read_timeout(client, COMMAND_READ_LARGE_TIMEOUT, &old_timeout);
974986
if (IMAP_HAS_CAPABILITY(client, IMAP_CAPABILITY_LIST_STATUS) && IMAP_HAS_CAPABILITY(client, IMAP_CAPABILITY_STATUS_SIZE)) {
975987
struct list_status_cb cb = {
976988
.buf = list_status_buf,
@@ -989,13 +1001,14 @@ static int __client_list(struct client *client)
9891001
} else {
9901002
res = mailimap_list(client->imap, "", "*", &imap_list);
9911003
}
1004+
set_command_read_timeout(client, old_timeout, NULL); /* Restore */
9921005

9931006
if (res != MAILIMAP_NO_ERROR) {
9941007
log_mailimap_error(client, res, "LIST failed");
9951008
return -1;
9961009
}
9971010
if (!clist_begin(imap_list)) {
998-
client_error("List is empty?");
1011+
client_error("LIST response is empty?");
9991012
mailimap_list_result_free(imap_list);
10001013
return -1;
10011014
}

0 commit comments

Comments
 (0)