changeset 22756:1f74202e9f70

lib-imap-client: Avoid "Unknown tag" errors for aborted commands If mailbox is closed before all command replies were received, the commands were aborted but they'll still receive the replies from server. Remember the aborted commands' tag numbers so they can be ignored.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Wed, 17 Jan 2018 15:03:06 +0200
parents 59ac434fef3f
children ffccd1d964b3
files src/lib-imap-client/imapc-connection.c
diffstat 1 files changed, 21 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-imap-client/imapc-connection.c	Wed Jan 17 15:02:09 2018 +0200
+++ b/src/lib-imap-client/imapc-connection.c	Wed Jan 17 15:03:06 2018 +0200
@@ -64,6 +64,8 @@
 	unsigned int idle:1;
 	/* Waiting for '+' literal reply before we can continue */
 	unsigned int wait_for_literal:1;
+	/* Command is fully sent to server */
+	unsigned int sent:1;
 };
 ARRAY_DEFINE_TYPE(imapc_command, struct imapc_command *);
 
@@ -114,6 +116,9 @@
 	ARRAY_TYPE(imapc_command) cmd_send_queue;
 	/* commands that have been sent, waiting for their tagged reply */
 	ARRAY_TYPE(imapc_command) cmd_wait_list;
+	/* commands that were already sent, but were aborted since (due to
+	   unselecting mailbox). */
+	ARRAY_TYPE(seq_range) aborted_cmd_tags;
 	unsigned int reconnect_command_count;
 
 	unsigned int ips_count, prev_connect_idx;
@@ -202,6 +207,7 @@
 	i_array_init(&conn->cmd_send_queue, 8);
 	i_array_init(&conn->cmd_wait_list, 32);
 	i_array_init(&conn->literal_files, 4);
+	i_array_init(&conn->aborted_cmd_tags, 8);
 
 	if (client->set.debug)
 		i_debug("imapc(%s): Created new connection", conn->name);
@@ -234,6 +240,7 @@
 	array_free(&conn->cmd_send_queue);
 	array_free(&conn->cmd_wait_list);
 	array_free(&conn->literal_files);
+	array_free(&conn->aborted_cmd_tags);
 	imapc_client_unref(&conn->client);
 	i_free(conn->ips);
 	i_free(conn->name);
@@ -348,6 +355,11 @@
 	array_foreach(&tmp_array, cmdp) {
 		cmd = *cmdp;
 
+		if (cmd->sent && conn->state == IMAPC_CONNECTION_STATE_DONE) {
+			/* We're not disconnected, so the reply will still
+			   come. Remember that it needs to be ignored. */
+			seq_range_array_add(&conn->aborted_cmd_tags, cmd->tag);
+		}
 		cmd->callback(&reply, cmd->context);
 		imapc_command_free(cmd);
 	}
@@ -386,6 +398,7 @@
 			i_free(conn->ips);
 			conn->ips_count = 0;
 		}
+		array_clear(&conn->aborted_cmd_tags);
 		conn->idling = FALSE;
 		conn->idle_plus_waiting = FALSE;
 		conn->idle_stopping = FALSE;
@@ -1435,6 +1448,13 @@
 		timeout_remove(&conn->to);
 
 	if (cmd == NULL) {
+		if (seq_range_exists(&conn->aborted_cmd_tags, conn->cur_tag)) {
+			/* sent command was already aborted - ignore it */
+			seq_range_array_remove(&conn->aborted_cmd_tags,
+					       conn->cur_tag);
+			imapc_connection_input_reset(conn);
+			return 1;
+		}
 		imapc_connection_input_error(conn,
 			"Unknown tag in a reply: %u %s %s",
 			conn->cur_tag, line, reply.text_full);
@@ -1988,6 +2008,7 @@
 
 	if (cmd->idle)
 		conn->idle_plus_waiting = TRUE;
+	cmd->sent = TRUE;
 
 	/* everything sent. move command to wait list. */
 	cmdp = array_idx(&conn->cmd_send_queue, 0);