IMAP: add support for CAPABILITY in LOGIN tagged OK response.

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 "[email protected]" "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
[email protected]
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 <[email protected]>
Reviewed-by: Adrien Destugues <[email protected]>
Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
Philippe Houdoin
2026-04-10 09:22:56 +00:00
parent ea4aa5544f
commit a65f8374f2
4 changed files with 30 additions and 3 deletions
@@ -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<StringArgument*>(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 -
@@ -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; }
@@ -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());
@@ -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;
}