Skip to content

Commit d7c585a

Browse files
committed
Added fallback logic for imap.strato.de if LIST doesn't return the INBOX
Fixes issue #1957
1 parent baacde5 commit d7c585a

File tree

8 files changed

+180
-0
lines changed

8 files changed

+180
-0
lines changed

MailKit/Net/Imap/ImapEngine.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3686,6 +3686,32 @@ public void QuerySpecialFolders (CancellationToken cancellationToken)
36863686

36873687
ProcessListInboxResponse (ic, command, list);
36883688

3689+
if (Inbox == null) {
3690+
if ((Capabilities & ImapCapabilities.ListExtended) != 0) {
3691+
// Note: This is a work-around for IMAP servers such as imap.strato.de which do not return a list of folders
3692+
// for the `LIST "" "INBOX" RETURN (SUBSCRIBED CHILDREN)` command. Disable the LIST-EXTENDED (and dependent)
3693+
// capabilities since they are clearly broken.
3694+
//
3695+
// See https://github.com/jstedfast/MailKit/issues/1957 for details.
3696+
Capabilities &= ~(ImapCapabilities.ListExtended | ImapCapabilities.ListStatus | ImapCapabilities.SpecialUse);
3697+
3698+
// Send a vanilla `LIST "" "INBOX"` command to get the INBOX folder.
3699+
ic = QueueListInboxCommand (cancellationToken, out command, out list);
3700+
3701+
Run (ic);
3702+
3703+
ProcessListInboxResponse (ic, command, list);
3704+
}
3705+
3706+
if (Inbox == null) {
3707+
// If we still don't have the INBOX folder, just create a placeholder for it.
3708+
char delim = PersonalNamespaces.Count > 0 ? PersonalNamespaces[0].DirectorySeparator : '/';
3709+
var inbox = CreateImapFolder ("INBOX", FolderAttributes.Inbox, delim);
3710+
CacheFolder (inbox);
3711+
Inbox = inbox;
3712+
}
3713+
}
3714+
36893715
if ((Capabilities & ImapCapabilities.SpecialUse) != 0) {
36903716
ic = QueueListSpecialUseCommand (command, list, cancellationToken);
36913717

@@ -3717,6 +3743,32 @@ public async Task QuerySpecialFoldersAsync (CancellationToken cancellationToken)
37173743

37183744
ProcessListInboxResponse (ic, command, list);
37193745

3746+
if (Inbox == null) {
3747+
if ((Capabilities & ImapCapabilities.ListExtended) != 0) {
3748+
// Note: This is a work-around for IMAP servers such as imap.strato.de which do not return a list of folders
3749+
// for the `LIST "" "INBOX" RETURN (SUBSCRIBED CHILDREN)` command. Disable the LIST-EXTENDED (and dependent)
3750+
// capabilities since they are clearly broken.
3751+
//
3752+
// See https://github.com/jstedfast/MailKit/issues/1957 for details.
3753+
Capabilities &= ~(ImapCapabilities.ListExtended | ImapCapabilities.ListStatus | ImapCapabilities.SpecialUse);
3754+
3755+
// Send a vanilla `LIST "" "INBOX"` command to get the INBOX folder.
3756+
ic = QueueListInboxCommand (cancellationToken, out command, out list);
3757+
3758+
await RunAsync (ic).ConfigureAwait (false);
3759+
3760+
ProcessListInboxResponse (ic, command, list);
3761+
}
3762+
3763+
if (Inbox == null) {
3764+
// If we still don't have the INBOX folder, just create a placeholder for it.
3765+
char delim = PersonalNamespaces.Count > 0 ? PersonalNamespaces[0].DirectorySeparator : '/';
3766+
var inbox = CreateImapFolder ("INBOX", FolderAttributes.Inbox, delim);
3767+
CacheFolder (inbox);
3768+
Inbox = inbox;
3769+
}
3770+
}
3771+
37203772
if ((Capabilities & ImapCapabilities.SpecialUse) != 0) {
37213773
ic = QueueListSpecialUseCommand (command, list, cancellationToken);
37223774

UnitTests/Net/Imap/ImapClientTests.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,8 @@ public void TestInvalidUntaggedBadResponse ()
19871987

19881988
Assert.That (alerts, Is.EqualTo (5), $"Unexpected number of alerts: {alerts}");
19891989

1990+
Assert.That (client.Inbox, Is.Not.Null, "Inbox");
1991+
19901992
client.Disconnect (false);
19911993
}
19921994
}
@@ -2021,6 +2023,8 @@ public async Task TestInvalidUntaggedBadResponseAsync ()
20212023

20222024
Assert.That (alerts, Is.EqualTo (5), $"Unexpected number of alerts: {alerts}");
20232025

2026+
Assert.That (client.Inbox, Is.Not.Null, "Inbox");
2027+
20242028
await client.DisconnectAsync (false);
20252029
}
20262030
}
@@ -7643,6 +7647,121 @@ public async Task TestNamespaceExtensionsAsync ()
76437647
}
76447648
}
76457649

7650+
static List<ImapReplayCommand> CreateListInboxFallbackAfterEmptyListExtendedCommands ()
7651+
{
7652+
return new List<ImapReplayCommand> {
7653+
new ImapReplayCommand ("", "strato.de.greeting.txt"),
7654+
new ImapReplayCommand ("A00000000 AUTHENTICATE PLAIN\r\n", ImapReplayCommandResponse.Plus),
7655+
new ImapReplayCommand ("A00000000", "AHVzZXJuYW1lAHBhc3N3b3Jk\r\n", "strato.de.authenticate.txt"),
7656+
new ImapReplayCommand ("A00000001 CAPABILITY\r\n", "strato.de.capability.txt"),
7657+
new ImapReplayCommand ("A00000002 NAMESPACE\r\n", "strato.de.namespace.txt"),
7658+
new ImapReplayCommand ("A00000003 LIST \"\" \"INBOX\" RETURN (SUBSCRIBED CHILDREN)\r\n", ImapReplayCommandResponse.OK),
7659+
new ImapReplayCommand ("A00000004 LIST \"\" \"INBOX\"\r\n", "strato.de.list-inbox.txt"),
7660+
new ImapReplayCommand ("A00000005 XLIST \"\" \"*\"\r\n", "strato.de.xlist.txt"),
7661+
new ImapReplayCommand ("A00000006 LOGOUT\r\n", ImapReplayCommandResponse.OK)
7662+
};
7663+
}
7664+
7665+
[Test]
7666+
public void TestListInboxFallbackAfterEmptyListExtended ()
7667+
{
7668+
const ImapCapabilities InitialCapabilities = ImapCapabilities.IMAP4 | ImapCapabilities.IMAP4rev1 | ImapCapabilities.Status |
7669+
ImapCapabilities.AppendLimit | ImapCapabilities.Enable | ImapCapabilities.Id | ImapCapabilities.Idle | ImapCapabilities.Move |
7670+
ImapCapabilities.ListExtended | ImapCapabilities.Namespace | ImapCapabilities.Quota | ImapCapabilities.Sort |
7671+
ImapCapabilities.SpecialUse | ImapCapabilities.UidPlus;
7672+
const ImapCapabilities AuthenticatedCapabilities = ImapCapabilities.IMAP4 | ImapCapabilities.IMAP4rev1 | ImapCapabilities.Status |
7673+
ImapCapabilities.AppendLimit | ImapCapabilities.CreateSpecialUse | ImapCapabilities.Quota | ImapCapabilities.Children |
7674+
ImapCapabilities.CondStore | ImapCapabilities.Enable | ImapCapabilities.ESort | ImapCapabilities.ESearch | ImapCapabilities.I18NLevel |
7675+
ImapCapabilities.Id | ImapCapabilities.Idle | ImapCapabilities.Move | /*ImapCapabilities.ListStatus | ImapCapabilities.ListExtended |*/
7676+
ImapCapabilities.LiteralPlus | ImapCapabilities.Namespace | /*ImapCapabilities.Preview |*/ ImapCapabilities.FuzzySearch |
7677+
ImapCapabilities.Sort | ImapCapabilities.SearchResults | /*ImapCapabilities.SpecialUse |*/ ImapCapabilities.StatusSize |
7678+
ImapCapabilities.UidPlus | ImapCapabilities.Unselect | ImapCapabilities.Within | ImapCapabilities.XList;
7679+
var commands = CreateListInboxFallbackAfterEmptyListExtendedCommands ();
7680+
7681+
using (var client = new ImapClient () { TagPrefix = 'A' }) {
7682+
try {
7683+
client.Connect (new ImapReplayStream (commands, false), "localhost", 143, SecureSocketOptions.None);
7684+
} catch (Exception ex) {
7685+
Assert.Fail ($"Did not expect an exception in Connect: {ex}");
7686+
}
7687+
7688+
Assert.That (client.IsConnected, Is.True, "Client failed to connect.");
7689+
7690+
Assert.That (client.Capabilities, Is.EqualTo (InitialCapabilities));
7691+
Assert.That (client.AppendLimit, Is.EqualTo (104857600), "AppendLimit");
7692+
Assert.That (client.AuthenticationMechanisms, Has.Count.EqualTo (2));
7693+
Assert.That (client.AuthenticationMechanisms, Does.Contain ("PLAIN"), "Expected SASL PLAIN auth mechanism");
7694+
Assert.That (client.AuthenticationMechanisms, Does.Contain ("LOGIN"), "Expected SASL LOGIN auth mechanism");
7695+
7696+
try {
7697+
client.Authenticate ("username", "password");
7698+
} catch (Exception ex) {
7699+
Assert.Fail ($"Did not expect an exception in Authenticate: {ex}");
7700+
}
7701+
7702+
Assert.That (client.Capabilities, Is.EqualTo (AuthenticatedCapabilities));
7703+
Assert.That (client.PersonalNamespaces, Has.Count.EqualTo (1), "PersonalNamespaces.Count");
7704+
Assert.That (client.PersonalNamespaces[0].Path, Is.EqualTo (string.Empty), "PersonalNamespaces[0].Path");
7705+
Assert.That (client.PersonalNamespaces[0].DirectorySeparator, Is.EqualTo ('.'), "PersonalNamespaces[0].DirectorySeparator");
7706+
Assert.That (client.OtherNamespaces, Has.Count.EqualTo (0), "OtherNamespaces.Count");
7707+
Assert.That (client.SharedNamespaces, Has.Count.EqualTo (0), "SharedNamespaces.Count");
7708+
7709+
Assert.That (client.Inbox, Is.Not.Null, "Inbox");
7710+
7711+
client.Disconnect (true);
7712+
}
7713+
}
7714+
7715+
[Test]
7716+
public async Task TestListInboxFallbackAfterEmptyListExtendedAsync ()
7717+
{
7718+
const ImapCapabilities InitialCapabilities = ImapCapabilities.IMAP4 | ImapCapabilities.IMAP4rev1 | ImapCapabilities.Status |
7719+
ImapCapabilities.AppendLimit | ImapCapabilities.Enable | ImapCapabilities.Id | ImapCapabilities.Idle | ImapCapabilities.Move |
7720+
ImapCapabilities.ListExtended | ImapCapabilities.Namespace | ImapCapabilities.Quota | ImapCapabilities.Sort |
7721+
ImapCapabilities.SpecialUse | ImapCapabilities.UidPlus;
7722+
const ImapCapabilities AuthenticatedCapabilities = ImapCapabilities.IMAP4 | ImapCapabilities.IMAP4rev1 | ImapCapabilities.Status |
7723+
ImapCapabilities.AppendLimit | ImapCapabilities.CreateSpecialUse | ImapCapabilities.Quota | ImapCapabilities.Children |
7724+
ImapCapabilities.CondStore | ImapCapabilities.Enable | ImapCapabilities.ESort | ImapCapabilities.ESearch | ImapCapabilities.I18NLevel |
7725+
ImapCapabilities.Id | ImapCapabilities.Idle | ImapCapabilities.Move | /*ImapCapabilities.ListStatus | ImapCapabilities.ListExtended |*/
7726+
ImapCapabilities.LiteralPlus | ImapCapabilities.Namespace | /*ImapCapabilities.Preview |*/ ImapCapabilities.FuzzySearch |
7727+
ImapCapabilities.Sort | ImapCapabilities.SearchResults | /*ImapCapabilities.SpecialUse |*/ ImapCapabilities.StatusSize |
7728+
ImapCapabilities.UidPlus | ImapCapabilities.Unselect | ImapCapabilities.Within | ImapCapabilities.XList;
7729+
var commands = CreateListInboxFallbackAfterEmptyListExtendedCommands ();
7730+
7731+
using (var client = new ImapClient () { TagPrefix = 'A' }) {
7732+
try {
7733+
await client.ConnectAsync (new ImapReplayStream (commands, true), "localhost", 143, SecureSocketOptions.None);
7734+
} catch (Exception ex) {
7735+
Assert.Fail ($"Did not expect an exception in Connect: {ex}");
7736+
}
7737+
7738+
Assert.That (client.IsConnected, Is.True, "Client failed to connect.");
7739+
7740+
Assert.That (client.Capabilities, Is.EqualTo (InitialCapabilities));
7741+
Assert.That (client.AppendLimit, Is.EqualTo (104857600), "AppendLimit");
7742+
Assert.That (client.AuthenticationMechanisms, Has.Count.EqualTo (2));
7743+
Assert.That (client.AuthenticationMechanisms, Does.Contain ("PLAIN"), "Expected SASL PLAIN auth mechanism");
7744+
Assert.That (client.AuthenticationMechanisms, Does.Contain ("LOGIN"), "Expected SASL LOGIN auth mechanism");
7745+
7746+
try {
7747+
await client.AuthenticateAsync ("username", "password");
7748+
} catch (Exception ex) {
7749+
Assert.Fail ($"Did not expect an exception in Authenticate: {ex}");
7750+
}
7751+
7752+
Assert.That (client.Capabilities, Is.EqualTo (AuthenticatedCapabilities));
7753+
Assert.That (client.PersonalNamespaces, Has.Count.EqualTo (1), "PersonalNamespaces.Count");
7754+
Assert.That (client.PersonalNamespaces[0].Path, Is.EqualTo (string.Empty), "PersonalNamespaces[0].Path");
7755+
Assert.That (client.PersonalNamespaces[0].DirectorySeparator, Is.EqualTo ('.'), "PersonalNamespaces[0].DirectorySeparator");
7756+
Assert.That (client.OtherNamespaces, Has.Count.EqualTo (0), "OtherNamespaces.Count");
7757+
Assert.That (client.SharedNamespaces, Has.Count.EqualTo (0), "SharedNamespaces.Count");
7758+
7759+
Assert.That (client.Inbox, Is.Not.Null, "Inbox");
7760+
7761+
await client.DisconnectAsync (true);
7762+
}
7763+
}
7764+
76467765
[Test]
76477766
public void TestLowercaseImapResponses ()
76487767
{
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A######## OK User logged in (234)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* CAPABILITY IMAP4 IMAP4rev1 APPENDLIMIT=104857600 AUTH=PLAIN AUTH=LOGIN CREATE-SPECIAL-USE QUOTA=RES-MAILBOX CHILDREN CONDSTORE ENABLE ESORT ESEARCH I18NLEVEL=2 ID IDLE MOVE LIST-STATUS LIST-EXTENDED LITERAL+ NAMESPACE PREVIEW=FUZZY QUOTA SEARCH=FUZZY SORT SEARCHRES SNIPPET=FUZZY SPECIAL-USE STATUS=SIZE UIDPLUS UNSELECT WITHIN XLIST
2+
A######## OK CAPABILITY completed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* OK [CAPABILITY IMAP4 IMAP4rev1 APPENDLIMIT=104857600 AUTH=PLAIN AUTH=LOGIN ENABLE ID IDLE MOVE LIST-EXTENDED NAMESPACE QUOTA SORT SPECIAL-USE UIDPLUS] IMAP server ready (P2 TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* LIST (\HasChildren) "." "INBOX"
2+
A######## OK LIST completed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* NAMESPACE (("" ".")) NIL NIL
2+
A######## OK NAMESPACE completed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A######## OK XLIST completed

0 commit comments

Comments
 (0)