@@ -283,44 +283,51 @@ static void parse_status(struct client *client, struct mailbox *mbox, char *rest
283283return ;
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
326333int 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
329336struct mailimap_status_att_list * att_list ;
330337struct mailimap_mailbox_data_status * status ;
331338clistiter * cur ;
339+ time_t old_timeout ;
332340
333341/* If LIST-STATUS is supported, we might not need to make a STATUS request at all */
334342if (list_status_resp ) {
@@ -374,7 +382,9 @@ int client_status_command(struct client *client, struct mailbox *mbox, char *res
374382log_mailimap_error (client , res , "Failed to construct STATUS" );
375383goto cleanup ;
376384}
385+ set_command_read_timeout (client , COMMAND_READ_LARGE_TIMEOUT , & old_timeout );
377386res = mailimap_status (client -> imap , mbox -> name , att_list , & status );
387+ set_command_read_timeout (client , old_timeout , NULL );
378388
379389if (res != MAILIMAP_NO_ERROR ) {
380390log_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. */
964974static 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
967978if (test_cmp (client )) {
@@ -971,6 +982,7 @@ static int __client_list(struct client *client)
971982
972983client_status ("Querying mailbox list" );
973984
985+ set_command_read_timeout (client , COMMAND_READ_LARGE_TIMEOUT , & old_timeout );
974986if (IMAP_HAS_CAPABILITY (client , IMAP_CAPABILITY_LIST_STATUS ) && IMAP_HAS_CAPABILITY (client , IMAP_CAPABILITY_STATUS_SIZE )) {
975987struct list_status_cb cb = {
976988.buf = list_status_buf ,
@@ -989,13 +1001,14 @@ static int __client_list(struct client *client)
9891001} else {
9901002res = mailimap_list (client -> imap , "" , "*" , & imap_list );
9911003}
1004+ set_command_read_timeout (client , old_timeout , NULL ); /* Restore */
9921005
9931006if (res != MAILIMAP_NO_ERROR ) {
9941007log_mailimap_error (client , res , "LIST failed" );
9951008return -1 ;
9961009}
9971010if (!clist_begin (imap_list )) {
998- client_error ("List is empty?" );
1011+ client_error ("LIST response is empty?" );
9991012mailimap_list_result_free (imap_list );
10001013return -1 ;
10011014}
0 commit comments