From a65f8374f258cdf81b495c7c7f1c37a7c41d4449 Mon Sep 17 00:00:00 2001 From: Philippe Houdoin Date: Wed, 8 Apr 2026 23:25:11 +0200 Subject: [PATCH] IMAP: add support for CAPABILITY in LOGIN tagged OK response. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per RFC 3501, a server MAY include a CAPABILITY response code in the tagged OK response to a successful LOGIN in order to send capabilities automatically. The capabilities in the untagged response could be truncated to a shorter list of capabilities meaningful to the login process. For instance, the IMAP4rev1 server of mailo.com, a french mail provider, returns this on LOGIN command: C: A0000001 LOGIN "xxxxxx.xxxxxx@mailo.com" "XXXXXXXXX" S: * OK [CAPABILITY IMAP4rev1 ID LITERAL+ AUTH=LOGIN AUTH=PLAIN STARTTLS] IMAP4rev1 MAILO Ready CAPABILITY: IMAP4rev1, ID, LITERAL+, AUTH=LOGIN, AUTH=PLAIN, STARTTLS S: A0000001 OK [CAPABILITY IMAP4rev1 ID LITERAL+ ENABLE IDLE QUOTA NAMESPACE SORT UIDPLUS UNSELECT UTF8=ACCEPT] Welcome xxxxxx.xxxxxx@mailo.com CAPABILITY: IMAP4rev1, ID, LITERAL+, AUTH=LOGIN, AUTH=PLAIN, STARTTLS, ENABLE, IDLE, QUOTA, NAMESPACE, SORT, UIDPLUS, UNSELECT, UTF8=ACCEPT capabilities: IMAP4rev1, ID, LITERAL+, AUTH=LOGIN, AUTH=PLAIN, STARTTLS, ENABLE, IDLE, QUOTA, NAMESPACE, SORT, UIDPLUS, UNSELECT, UTF8=ACCEPT As the explicit CAPABILITY command is not sent if capabilities were retrieved from LOGIN response, before this change, the complete capabilities were not retrieved. This change merge capabilities from both untagged response and tagged OK response of LOGIN command. Also, improve server responses traces, following the RFC S: prefix. Change-Id: Id0fc18f0913ab03d2d3f5f90b296fbb7fb89446f Reviewed-on: https://review.haiku-os.org/c/haiku/+/10703 Reviewed-by: Jérôme Duval Reviewed-by: Adrien Destugues Tested-by: Commit checker robot --- .../imap/imap_lib/Commands.cpp | 25 +++++++++++++++++++ .../imap/imap_lib/Commands.h | 1 + .../imap/imap_lib/Protocol.cpp | 5 ++-- .../imap/imap_lib/Response.cpp | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.cpp index eac96f82a8..555bbf4c30 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.cpp +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.cpp @@ -193,6 +193,31 @@ LoginCommand::HandleUntagged(Response& response) } +status_t +LoginCommand::HandleTagged(Response& response) +{ + if (!response.EqualsAt(0, "OK") || !response.IsListAt(1, '[')) + return Command::HandleTagged(response); + + // RFC 3501 (IMAP4rev1): + // A server MAY include a CAPABILITY response code in the tagged OK + // response to a successful LOGIN command in order to send + // capabilities automatically. + ArgumentList& list = response.ListAt(1); + if (!list.EqualsAt(0, "CAPABILITY")) + return false; + + // merge with previous capabilities extracted from command untagged response, if any + while (list.CountItems() > 1) { + StringArgument* argument = dynamic_cast(list.RemoveItemAt(1)); + if (argument != NULL && !fCapabilities.Contains(argument->ToString().String())) + fCapabilities.AddItem(argument); + } + + TRACE("CAPABILITY: %s\n", fCapabilities.ToString().String()); + return B_OK; +} + // #pragma mark - diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h index 902d955f51..f32ae1e6e2 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.h @@ -82,6 +82,7 @@ public: virtual BString CommandString(); virtual bool HandleUntagged(Response& response); + virtual status_t HandleTagged(Response& response); const ArgumentList& Capabilities() const { return fCapabilities; } diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp index e7030dcd0d..039543c1cc 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp @@ -321,6 +321,7 @@ Protocol::HandleResponse(Command* command, bigtime_t timeout, bool done = false; while (!done) { try { + TRACE("S: "); status_t status = parser.NextResponse(response, timeout); if (status != B_OK) { // we might have lost the connection, clear the connection state @@ -339,7 +340,7 @@ Protocol::HandleResponse(Command* command, bigtime_t timeout, } } if (!handled) - printf("Unhandled S: %s\n", response.ToString().String()); + TRACE(" => Unhandled\n"); } else { CommandIDMap::iterator found = fOngoingCommands.find(response.Tag()); @@ -350,7 +351,7 @@ Protocol::HandleResponse(Command* command, bigtime_t timeout, fOngoingCommands.erase(found); } else - printf("Unknown tag S: %s\n", response.ToString().String()); + TRACE(" => Unknown tag\n"); } } catch (ParseException& exception) { printf("Error during parsing: %s\n", exception.Message()); diff --git a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp index 9131080ca7..349ab0ae63 100644 --- a/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp +++ b/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp @@ -701,7 +701,7 @@ Response::Read(BDataIO& stream) char c; ssize_t bytesRead = stream.Read(&c, 1); if (bytesRead == 1) { - printf("%c", c); + TRACE("%c", c); return c; }