From d232175ed74174c609cb9add0479c76938082739 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Thu, 28 Dec 2023 12:50:56 -0500 Subject: [PATCH] Split ImapEngine's UpdateStatus/UpdateStatusAsync Part of an ongoing effort to fix issue #1335 --- MailKit/Net/Imap/ImapEngine.cs | 194 +++++++++++++++++++++------------ 1 file changed, 126 insertions(+), 68 deletions(-) diff --git a/MailKit/Net/Imap/ImapEngine.cs b/MailKit/Net/Imap/ImapEngine.cs index 9f672d4649..01b03fbb33 100644 --- a/MailKit/Net/Imap/ImapEngine.cs +++ b/MailKit/Net/Imap/ImapEngine.cs @@ -899,16 +899,6 @@ internal Task ReadLineAsync (bool doAsync, CancellationToken cancellatio return Task.FromResult (ReadLine (cancellationToken)); } - internal ValueTask ReadTokenAsync (string specials, bool doAsync, CancellationToken cancellationToken) - { - if (doAsync) - return Stream.ReadTokenAsync (specials, cancellationToken); - - var token = Stream.ReadToken (specials, cancellationToken); - - return new ValueTask (token); - } - internal ValueTask ReadTokenAsync (bool doAsync, CancellationToken cancellationToken) { if (doAsync) @@ -1195,16 +1185,6 @@ public async Task ReadLiteralAsync (CancellationToken cancellationToken) } } - Task ReadLiteralAsync (bool doAsync, CancellationToken cancellationToken) - { - if (doAsync) - return ReadLiteralAsync (cancellationToken); - - var value = ReadLiteral (cancellationToken); - - return Task.FromResult (value); - } - void SkipLine (CancellationToken cancellationToken) { ImapToken token; @@ -2111,16 +2091,79 @@ public async ValueTask ParseResponseCodeAsync (bool isTagged, return code; } - async ValueTask UpdateStatusAsync (bool doAsync, CancellationToken cancellationToken) + bool UpdateSimpleStatusValue (ImapFolder folder, string atom, ImapToken token) { - var token = await ReadTokenAsync (ImapStream.AtomSpecials, doAsync, cancellationToken).ConfigureAwait (false); uint count, uid; ulong modseq; + + if (atom.Equals ("HIGHESTMODSEQ", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + modseq = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.UpdateHighestModSeq (modseq); + } else if (atom.Equals ("MESSAGES", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.OnExists ((int) count); + } else if (atom.Equals ("RECENT", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.OnRecent ((int) count); + } else if (atom.Equals ("UIDNEXT", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.UpdateUidNext (uid > 0 ? new UniqueId (uid) : UniqueId.Invalid); + } else if (atom.Equals ("UIDVALIDITY", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.UpdateUidValidity (uid); + } else if (atom.Equals ("UNSEEN", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.UpdateUnread ((int) count); + } else if (atom.Equals ("APPENDLIMIT", StringComparison.OrdinalIgnoreCase)) { + if (token.Type == ImapTokenType.Atom) { + var limit = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.UpdateAppendLimit (limit); + } else { + AssertToken (token, ImapTokenType.Nil, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + folder?.UpdateAppendLimit (null); + } + } else if (atom.Equals ("SIZE", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + var size = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token); + + folder?.UpdateSize (size); + } else { + // This is probably the MAILBOXID value which is multiple tokens and can't be handled here. + return false; + } + + return true; + } + + void UpdateStatus (CancellationToken cancellationToken) + { + var token = ReadToken (ImapStream.AtomSpecials, cancellationToken); string name; switch (token.Type) { case ImapTokenType.Literal: - name = await ReadLiteralAsync (doAsync, cancellationToken).ConfigureAwait (false); + name = ReadLiteral (cancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: @@ -2138,12 +2181,12 @@ async ValueTask UpdateStatusAsync (bool doAsync, CancellationToken cancellationT // and hasn't yet requested the folder. That's ok. TryGetCachedFolder (name, out var folder); - token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); + token = ReadToken (cancellationToken); AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); do { - token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); + token = ReadToken (cancellationToken); if (token.Type == ImapTokenType.CloseParen) break; @@ -2152,76 +2195,91 @@ async ValueTask UpdateStatusAsync (bool doAsync, CancellationToken cancellationT var atom = (string) token.Value; - token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); + token = ReadToken (cancellationToken); - if (atom.Equals ("HIGHESTMODSEQ", StringComparison.OrdinalIgnoreCase)) { - AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + if (UpdateSimpleStatusValue (folder, atom, token)) + continue; - modseq = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token); + if (atom.Equals ("MAILBOXID", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); - folder?.UpdateHighestModSeq (modseq); - } else if (atom.Equals ("MESSAGES", StringComparison.OrdinalIgnoreCase)) { - AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + token = ReadToken (cancellationToken); - count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + AssertToken (token, ImapTokenType.Atom, GenericItemSyntaxErrorFormat, atom, token); - folder?.OnExists ((int) count); - } else if (atom.Equals ("RECENT", StringComparison.OrdinalIgnoreCase)) { - AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + folder?.UpdateId ((string) token.Value); - count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + token = ReadToken (cancellationToken); - folder?.OnRecent ((int) count); - } else if (atom.Equals ("UIDNEXT", StringComparison.OrdinalIgnoreCase)) { - AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + AssertToken (token, ImapTokenType.CloseParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + } + } while (true); - uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + token = ReadToken (cancellationToken); - folder?.UpdateUidNext (uid > 0 ? new UniqueId (uid) : UniqueId.Invalid); - } else if (atom.Equals ("UIDVALIDITY", StringComparison.OrdinalIgnoreCase)) { - AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + AssertToken (token, ImapTokenType.Eoln, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + } - uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + async ValueTask UpdateStatusAsync (CancellationToken cancellationToken) + { + var token = await ReadTokenAsync (ImapStream.AtomSpecials, cancellationToken).ConfigureAwait (false); + string name; - folder?.UpdateUidValidity (uid); - } else if (atom.Equals ("UNSEEN", StringComparison.OrdinalIgnoreCase)) { - AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + switch (token.Type) { + case ImapTokenType.Literal: + name = await ReadLiteralAsync (cancellationToken).ConfigureAwait (false); + break; + case ImapTokenType.QString: + case ImapTokenType.Atom: + name = (string) token.Value; + break; + case ImapTokenType.Nil: + // Note: according to rfc3501, section 4.5, NIL is acceptable as a mailbox name. + name = (string) token.Value; + break; + default: + throw UnexpectedToken (GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + } - count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + // Note: if the folder is null, then it probably means the user is using NOTIFY + // and hasn't yet requested the folder. That's ok. + TryGetCachedFolder (name, out var folder); - folder?.UpdateUnread ((int) count); - } else if (atom.Equals ("APPENDLIMIT", StringComparison.OrdinalIgnoreCase)) { - if (token.Type == ImapTokenType.Atom) { - var limit = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token); + token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false); - folder?.UpdateAppendLimit (limit); - } else { - AssertToken (token, ImapTokenType.Nil, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); - folder?.UpdateAppendLimit (null); - } - } else if (atom.Equals ("SIZE", StringComparison.OrdinalIgnoreCase)) { - AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + do { + token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false); - var size = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token); + if (token.Type == ImapTokenType.CloseParen) + break; - folder?.UpdateSize (size); - } else if (atom.Equals ("MAILBOXID", StringComparison.OrdinalIgnoreCase)) { + AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); + + var atom = (string) token.Value; + + token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false); + + if (UpdateSimpleStatusValue (folder, atom, token)) + continue; + + if (atom.Equals ("MAILBOXID", StringComparison.OrdinalIgnoreCase)) { AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); - token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); + token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false); AssertToken (token, ImapTokenType.Atom, GenericItemSyntaxErrorFormat, atom, token); folder?.UpdateId ((string) token.Value); - token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); + token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false); AssertToken (token, ImapTokenType.CloseParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); } } while (true); - token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); + token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false); AssertToken (token, ImapTokenType.Eoln, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token); } @@ -2326,7 +2384,7 @@ internal void ProcessUntaggedResponse (CancellationToken cancellationToken) } else if (atom.Equals ("NAMESPACE", StringComparison.OrdinalIgnoreCase)) { UpdateNamespacesAsync (false, cancellationToken).GetAwaiter ().GetResult (); } else if (atom.Equals ("STATUS", StringComparison.OrdinalIgnoreCase)) { - UpdateStatusAsync (false, cancellationToken).GetAwaiter ().GetResult (); + UpdateStatus (cancellationToken); } else if (IsOkNoOrBad (atom, out var result)) { token = ReadToken (cancellationToken); @@ -2479,7 +2537,7 @@ internal async Task ProcessUntaggedResponseAsync (CancellationToken cancellation } else if (atom.Equals ("NAMESPACE", StringComparison.OrdinalIgnoreCase)) { await UpdateNamespacesAsync (doAsync: true, cancellationToken).ConfigureAwait (false); } else if (atom.Equals ("STATUS", StringComparison.OrdinalIgnoreCase)) { - await UpdateStatusAsync (doAsync: true, cancellationToken).ConfigureAwait (false); + await UpdateStatusAsync (cancellationToken).ConfigureAwait (false); } else if (IsOkNoOrBad (atom, out var result)) { token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);