Skip to content

Commit

Permalink
Add work-around for iCloud responses to ENABLE QRESYNC
Browse files Browse the repository at this point in the history
Fixes issue #1871
  • Loading branch information
jstedfast committed Jan 18, 2025
1 parent 9415267 commit 0999c49
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 2 deletions.
11 changes: 10 additions & 1 deletion MailKit/Net/Imap/ImapClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,18 @@ bool TryQueueEnableQuickResyncCommand (CancellationToken cancellationToken, out
return true;
}

static void ProcessEnableResponse (ImapCommand ic)
void ProcessEnableResponse (ImapCommand ic)
{
ic.ThrowIfNotOk ("ENABLE");

if (engine.QuirksMode == ImapQuirksMode.iCloud) {
// Note: iCloud's response to the `ENABLE QRESYNC CONDSTORE` command does not include an untagged response
// notifying us that QRESYNC or CONDSTORE have been enabled. Instead, if we get a tagged OK response, we
// assume that these features were enabled successfully.
//
// See https://github.com/jstedfast/MailKit/issues/1871 for details.
engine.QResyncEnabled = true;
}
}

/// <summary>
Expand Down
106 changes: 106 additions & 0 deletions UnitTests/Net/Imap/ImapClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ public class ImapClientTests
ImapCapabilities.ESearch | ImapCapabilities.Compress | ImapCapabilities.Enable | ImapCapabilities.ListExtended |
ImapCapabilities.ListStatus | ImapCapabilities.Move | ImapCapabilities.UTF8Accept | ImapCapabilities.XList |
ImapCapabilities.GMailExt1 | ImapCapabilities.LiteralMinus | ImapCapabilities.AppendLimit;
static readonly ImapCapabilities ICloudInitialCapabilities = ImapCapabilities.IMAP4 | ImapCapabilities.IMAP4rev1 |
ImapCapabilities.Status | ImapCapabilities.SaslIR;
static readonly ImapCapabilities ICloudAuthenticatedCapabilities = ImapCapabilities.IMAP4 | ImapCapabilities.IMAP4rev1 |
ImapCapabilities.Status | ImapCapabilities.CondStore | ImapCapabilities.Enable | ImapCapabilities.QuickResync |
ImapCapabilities.Quota | ImapCapabilities.Namespace | ImapCapabilities.UidPlus | ImapCapabilities.Children |
ImapCapabilities.Binary | ImapCapabilities.Unselect | ImapCapabilities.Sort | ImapCapabilities.Catenate |
ImapCapabilities.Language | ImapCapabilities.ESearch | ImapCapabilities.ESort | ImapCapabilities.Thread |
ImapCapabilities.Context | ImapCapabilities.Within | ImapCapabilities.SaslIR | ImapCapabilities.SearchResults |
ImapCapabilities.Metadata | ImapCapabilities.Id | ImapCapabilities.Annotate | ImapCapabilities.MultiSearch |
ImapCapabilities.Idle | ImapCapabilities.ListStatus;
static readonly ImapCapabilities IMAP4rev2CoreCapabilities = ImapCapabilities.IMAP4rev2 | ImapCapabilities.Status |
ImapCapabilities.Namespace | ImapCapabilities.Unselect | ImapCapabilities.UidPlus | ImapCapabilities.ESearch |
ImapCapabilities.SearchResults | ImapCapabilities.Enable | ImapCapabilities.Idle | ImapCapabilities.SaslIR | ImapCapabilities.ListExtended |
Expand Down Expand Up @@ -3564,6 +3574,8 @@ public void TestEnableQuickResync ()

client.EnableQuickResync ();

Assert.That (client.Inbox.Supports (FolderFeature.QuickResync), Is.True, "Expected the INBOX to support QRESYNC");

// ENABLE QRESYNC a second time should no-op.
client.EnableQuickResync ();

Expand Down Expand Up @@ -3606,6 +3618,100 @@ public async Task TestEnableQuickResyncAsync ()

await client.EnableQuickResyncAsync ();

Assert.That (client.Inbox.Supports (FolderFeature.QuickResync), Is.True, "Expected the INBOX to support QRESYNC");

// ENABLE QRESYNC a second time should no-op.
await client.EnableQuickResyncAsync ();

await client.DisconnectAsync (false);
}
}

static List<ImapReplayCommand> CreateEnableQuickResynciCloudCommands ()
{
return new List<ImapReplayCommand> {
new ImapReplayCommand ("", "icloud.greeting.txt"),
new ImapReplayCommand ("A00000000 AUTHENTICATE PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk\r\n", "icloud.authenticate-plain.txt"),
new ImapReplayCommand ("A00000001 CAPABILITY\r\n", "icloud.capability.txt"),
new ImapReplayCommand ("A00000002 NAMESPACE\r\n", "icloud.namespace.txt"),
new ImapReplayCommand ("A00000003 LIST \"\" \"INBOX\"\r\n", "icloud.list-inbox.txt"),
new ImapReplayCommand ("A00000004 ENABLE QRESYNC CONDSTORE\r\n", "icloud.enable-qresync.txt"),
};
}

[Test]
public void TestEnableQuickResynciCloud ()
{
var commands = CreateEnableQuickResynciCloudCommands ();

using (var client = new ImapClient () { TagPrefix = 'A' }) {
try {
client.Connect (new ImapReplayStream (commands, false), "localhost", 143, SecureSocketOptions.None);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Connect: {ex}");
}

Assert.That (client.Capabilities, Is.EqualTo (ICloudInitialCapabilities));
Assert.That (client.AuthenticationMechanisms, Has.Count.EqualTo (4));
Assert.That (client.AuthenticationMechanisms, Does.Contain ("ATOKEN"), "Expected SASL ATOKEN auth mechanism");
Assert.That (client.AuthenticationMechanisms, Does.Contain ("PLAIN"), "Expected SASL PLAIN auth mechanism");
Assert.That (client.AuthenticationMechanisms, Does.Contain ("ATOKEN2"), "Expected SASL ATOKEN2 auth mechanism");
Assert.That (client.AuthenticationMechanisms, Does.Contain ("XOAUTH2"), "Expected SASL XOAUTH2 auth mechanism");

try {
client.Authenticate ("username", "password");
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Authenticate: {ex}");
}

Assert.That (client.Capabilities, Is.EqualTo (ICloudAuthenticatedCapabilities));
Assert.That (client.ThreadingAlgorithms, Does.Contain (ThreadingAlgorithm.OrderedSubject), "Expected THREAD=ORDEREDSUBJECT");
Assert.That (client.ThreadingAlgorithms, Does.Contain (ThreadingAlgorithm.References), "Expected THREAD=REFERENCES");

client.EnableQuickResync ();

Assert.That (client.Inbox.Supports (FolderFeature.QuickResync), Is.True, "Expected the INBOX to support QRESYNC");

// ENABLE QRESYNC a second time should no-op.
client.EnableQuickResync ();

client.Disconnect (false);
}
}

[Test]
public async Task TestEnableQuickResynciCloudAsync ()
{
var commands = CreateEnableQuickResynciCloudCommands ();

using (var client = new ImapClient () { TagPrefix = 'A' }) {
try {
await client.ConnectAsync (new ImapReplayStream (commands, true), "localhost", 143, SecureSocketOptions.None);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Connect: {ex}");
}

Assert.That (client.Capabilities, Is.EqualTo (ICloudInitialCapabilities));
Assert.That (client.AuthenticationMechanisms, Has.Count.EqualTo (4));
Assert.That (client.AuthenticationMechanisms, Does.Contain ("ATOKEN"), "Expected SASL ATOKEN auth mechanism");
Assert.That (client.AuthenticationMechanisms, Does.Contain ("PLAIN"), "Expected SASL PLAIN auth mechanism");
Assert.That (client.AuthenticationMechanisms, Does.Contain ("ATOKEN2"), "Expected SASL ATOKEN2 auth mechanism");
Assert.That (client.AuthenticationMechanisms, Does.Contain ("XOAUTH2"), "Expected SASL XOAUTH2 auth mechanism");

try {
await client.AuthenticateAsync ("username", "password");
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Authenticate: {ex}");
}

Assert.That (client.Capabilities, Is.EqualTo (ICloudAuthenticatedCapabilities));
Assert.That (client.ThreadingAlgorithms, Does.Contain (ThreadingAlgorithm.OrderedSubject), "Expected THREAD=ORDEREDSUBJECT");
Assert.That (client.ThreadingAlgorithms, Does.Contain (ThreadingAlgorithm.References), "Expected THREAD=REFERENCES");

await client.EnableQuickResyncAsync ();

Assert.That (client.Inbox.Supports (FolderFeature.QuickResync), Is.True, "Expected the INBOX to support QRESYNC");

// ENABLE QRESYNC a second time should no-op.
await client.EnableQuickResyncAsync ();

Expand Down
1 change: 1 addition & 0 deletions UnitTests/Net/Imap/Resources/icloud/authenticate-plain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A######## OK user username authenticated
2 changes: 2 additions & 0 deletions UnitTests/Net/Imap/Resources/icloud/capability.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* CAPABILITY XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 CONDSTORE ENABLE QRESYNC QUOTA XAPPLELITERAL NAMESPACE UIDPLUS CHILDREN BINARY UNSELECT SORT CATENATE URLAUTH LANGUAGE ESEARCH ESORT THREAD=ORDEREDSUBJECT THREAD=REFERENCES CONTEXT=SEARCH CONTEXT=SORT WITHIN SASL-IR SEARCHRES METADATA ID XMSEARCH X-SUN-SORT ANNOTATE-EXPERIMENT-1 X-UNAUTHENTICATE X-SUN-IMAP XUM1 MULTISEARCH IDLE X-APPLE-REMOTE-LINKS LIST-STATUS
A######## OK Completed
1 change: 1 addition & 0 deletions UnitTests/Net/Imap/Resources/icloud/enable-qresync.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A######## OK ENABLE completed
2 changes: 1 addition & 1 deletion UnitTests/Net/Imap/Resources/icloud/greeting.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* OK [CAPABILITY XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN] (2313B20-3ff771b405ab) pv50p00im-tygg10010601.me.com
* OK [CAPABILITY XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN AUTH=ATOKEN2 AUTH=XOAUTH2] (2428B47-26126cfe23cd) p00-iscream-f9f7bf749-qjr87
2 changes: 2 additions & 0 deletions UnitTests/Net/Imap/Resources/icloud/list-inbox.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* LIST (\Noinferiors) "/" "INBOX"
A######## OK LIST completed (took 0 ms)
2 changes: 2 additions & 0 deletions UnitTests/Net/Imap/Resources/icloud/namespace.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* NAMESPACE (("" "/")) NIL NIL
A######## OK NAMESPACE completed (took 0 ms)

0 comments on commit 0999c49

Please sign in to comment.