// // ImapFolderFetch.cs // // Author: Jeffrey Stedfast // // Copyright (c) 2013-2020 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Globalization; using System.Threading.Tasks; using System.Collections.Generic; using System.Collections.ObjectModel; using MimeKit; using MimeKit.IO; using MimeKit.Text; using MimeKit.Utils; using MailKit.Search; namespace MailKit.Net.Imap { public partial class ImapFolder { internal static readonly HashSet EmptyHeaderFields = new HashSet (); const int PreviewHtmlLength = 16 * 1024; const int PreviewTextLength = 512; class FetchSummaryContext { public readonly List Messages; public FetchSummaryContext () { Messages = new List (); } int BinarySearch (int index, bool insert) { int min = 0, max = Messages.Count; if (max == 0) return insert ? 0 : -1; do { int i = min + ((max - min) / 2); if (index == Messages[i].Index) return i; if (index > Messages[i].Index) { min = i + 1; } else { max = i; } } while (min < max); return insert ? min : -1; } public void Add (int index, MessageSummary message) { int i = BinarySearch (index, true); if (i < Messages.Count) Messages.Insert (i, message); else Messages.Add (message); } public bool TryGetValue (int index, out MessageSummary message) { int i; if ((i = BinarySearch (index, false)) == -1) { message = null; return false; } message = (MessageSummary) Messages[i]; return true; } public void OnMessageExpunged (object sender, MessageEventArgs args) { int index = BinarySearch (args.Index, true); if (index >= Messages.Count) return; if (Messages[index].Index == args.Index) Messages.RemoveAt (index); for (int i = index; i < Messages.Count; i++) { var message = (MessageSummary) Messages[i]; message.Index--; } } } static async Task ReadLiteralDataAsync (ImapEngine engine, bool doAsync, CancellationToken cancellationToken) { var buf = new byte[4096]; int nread; do { if (doAsync) nread = await engine.Stream.ReadAsync (buf, 0, buf.Length, cancellationToken).ConfigureAwait (false); else nread = engine.Stream.Read (buf, 0, buf.Length, cancellationToken); } while (nread > 0); } static async Task SkipParenthesizedList (ImapEngine engine, bool doAsync, CancellationToken cancellationToken) { do { var token = await engine.PeekTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.Eoln) return; // token is safe to read, so pop it off the queue await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.CloseParen) break; if (token.Type == ImapTokenType.OpenParen) { // skip the inner parenthesized list await SkipParenthesizedList (engine, doAsync, cancellationToken).ConfigureAwait (false); } } while (true); } async Task FetchSummaryItemsAsync (ImapEngine engine, MessageSummary message, bool doAsync, CancellationToken cancellationToken) { var token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.OpenParen, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); do { token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.CloseParen || token.Type == ImapTokenType.Eoln) break; bool parenthesized = false; if (engine.QuirksMode == ImapQuirksMode.Domino && token.Type == ImapTokenType.OpenParen) { // Note: Lotus Domino IMAP will (sometimes?) encapsulate the `ENVELOPE` segment of the // response within an extra set of parenthesis. // // See https://github.com/jstedfast/MailKit/issues/943 for details. token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); parenthesized = true; } ImapEngine.AssertToken (token, ImapTokenType.Atom, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var atom = (string) token.Value; string format; ulong value64; uint value; int idx; switch (atom.ToUpperInvariant ()) { case "INTERNALDATE": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); switch (token.Type) { case ImapTokenType.QString: case ImapTokenType.Atom: message.InternalDate = ImapUtils.ParseInternalDate ((string) token.Value); break; case ImapTokenType.Nil: message.InternalDate = null; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } message.Fields |= MessageSummaryItems.InternalDate; break; case "RFC822.SIZE": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); message.Size = ImapEngine.ParseNumber (token, false, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.Fields |= MessageSummaryItems.Size; break; case "BODYSTRUCTURE": format = string.Format (ImapEngine.GenericItemSyntaxErrorFormat, "BODYSTRUCTURE", "{0}"); message.Body = await ImapUtils.ParseBodyAsync (engine, format, string.Empty, doAsync, cancellationToken).ConfigureAwait (false); message.Fields |= MessageSummaryItems.BodyStructure; break; case "BODY": token = await engine.PeekTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); format = ImapEngine.FetchBodySyntaxErrorFormat; if (token.Type == ImapTokenType.OpenBracket) { var referencesField = false; var headerFields = false; // consume the '[' token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.OpenBracket, format, token); // References and/or other headers were requested... do { token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { do { token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.CloseParen) break; // the header field names will generally be atoms or qstrings but may also be literals engine.Stream.UngetToken (token); var field = await ImapUtils.ReadStringTokenAsync (engine, format, doAsync, cancellationToken).ConfigureAwait (false); if (headerFields && !referencesField && field.Equals ("REFERENCES", StringComparison.OrdinalIgnoreCase)) referencesField = true; } while (true); } else { ImapEngine.AssertToken (token, ImapTokenType.Atom, format, token); atom = (string) token.Value; headerFields = atom.Equals ("HEADER.FIELDS", StringComparison.OrdinalIgnoreCase); if (!headerFields && atom.Equals ("HEADER", StringComparison.OrdinalIgnoreCase)) { // if we're fetching *all* headers, then it will include the References header (if it exists) referencesField = true; } } } while (true); ImapEngine.AssertToken (token, ImapTokenType.CloseBracket, format, token); token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.Literal, format, token); try { message.Headers = await engine.ParseHeadersAsync (engine.Stream, doAsync, cancellationToken).ConfigureAwait (false); } catch (FormatException) { message.Headers = new HeaderList (); } // consume any remaining literal data... (typically extra blank lines) await ReadLiteralDataAsync (engine, doAsync, cancellationToken).ConfigureAwait (false); message.References = new MessageIdList (); if ((idx = message.Headers.IndexOf (HeaderId.References)) != -1) { var references = message.Headers[idx]; var rawValue = references.RawValue; foreach (var msgid in MimeUtils.EnumerateReferences (rawValue, 0, rawValue.Length)) message.References.Add (msgid); } message.Fields |= MessageSummaryItems.Headers; if (referencesField) message.Fields |= MessageSummaryItems.References; } else { message.Body = await ImapUtils.ParseBodyAsync (engine, format, string.Empty, doAsync, cancellationToken).ConfigureAwait (false); message.Fields |= MessageSummaryItems.Body; } break; case "ENVELOPE": message.Envelope = await ImapUtils.ParseEnvelopeAsync (engine, doAsync, cancellationToken).ConfigureAwait (false); message.Fields |= MessageSummaryItems.Envelope; break; case "FLAGS": message.Flags = await ImapUtils.ParseFlagsListAsync (engine, atom, message.Keywords, doAsync, cancellationToken).ConfigureAwait (false); message.Fields |= MessageSummaryItems.Flags; break; case "MODSEQ": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.OpenParen, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); value64 = ImapEngine.ParseNumber64 (token, false, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.CloseParen, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.Fields |= MessageSummaryItems.ModSeq; message.ModSeq = value64; if (value64 > HighestModSeq) UpdateHighestModSeq (value64); break; case "UID": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); value = ImapEngine.ParseNumber (token, true, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.UniqueId = new UniqueId (UidValidity, value); message.Fields |= MessageSummaryItems.UniqueId; break; case "EMAILID": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.OpenParen, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.Atom, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.Fields |= MessageSummaryItems.EmailId; message.EmailId = (string) token.Value; token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.CloseParen, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); break; case "THREADID": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.OpenParen) { token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.Atom, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.Fields |= MessageSummaryItems.ThreadId; message.ThreadId = (string) token.Value; token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.CloseParen, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } else { ImapEngine.AssertToken (token, ImapTokenType.Nil, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.Fields |= MessageSummaryItems.ThreadId; message.ThreadId = null; } break; case "X-GM-MSGID": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); value64 = ImapEngine.ParseNumber64 (token, true, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.Fields |= MessageSummaryItems.GMailMessageId; message.GMailMessageId = value64; break; case "X-GM-THRID": token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); value64 = ImapEngine.ParseNumber64 (token, true, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); message.Fields |= MessageSummaryItems.GMailThreadId; message.GMailThreadId = value64; break; case "X-GM-LABELS": message.GMailLabels = await ImapUtils.ParseLabelsListAsync (engine, doAsync, cancellationToken).ConfigureAwait (false); message.Fields |= MessageSummaryItems.GMailLabels; break; case "ANNOTATION": message.Annotations = await ImapUtils.ParseAnnotationsAsync (engine, doAsync, cancellationToken).ConfigureAwait (false); message.Fields |= MessageSummaryItems.Annotations; break; default: // Unexpected or unknown token (such as XAOL.SPAM.REASON or XAOL-MSGID). Simply read 1 more token (the argument) and ignore. token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.OpenParen) await SkipParenthesizedList (engine, doAsync, cancellationToken).ConfigureAwait (false); break; } if (parenthesized) { // Note: This is the second half of the Lotus Domino IMAP server work-around. token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.CloseParen, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); } } while (true); ImapEngine.AssertToken (token, ImapTokenType.CloseParen, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); } async Task FetchSummaryItemsAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { var ctx = (FetchSummaryContext) ic.UserData; MessageSummary message; if (!ctx.TryGetValue (index, out message)) { message = new MessageSummary (this, index); ctx.Add (index, message); } await FetchSummaryItemsAsync (engine, message, doAsync, ic.CancellationToken).ConfigureAwait (false); OnMessageSummaryFetched (message); } internal static string FormatSummaryItems (ImapEngine engine, ref MessageSummaryItems items, HashSet headers, out bool previewText, bool isNotify = false) { if ((items & MessageSummaryItems.PreviewText) != 0) { // if the user wants the preview text, we will also need the UIDs and BODYSTRUCTUREs // so that we can request a preview of the body text in subsequent FETCH requests. items |= MessageSummaryItems.BodyStructure | MessageSummaryItems.UniqueId; previewText = true; } else { previewText = false; } if ((items & MessageSummaryItems.BodyStructure) != 0 && (items & MessageSummaryItems.Body) != 0) { // don't query both the BODY and BODYSTRUCTURE, that's just dumb... items &= ~MessageSummaryItems.Body; } if (engine.QuirksMode != ImapQuirksMode.GMail && !isNotify) { // first, eliminate the aliases... var alias = items & ~MessageSummaryItems.PreviewText; if (alias == MessageSummaryItems.All) return "ALL"; if (alias == MessageSummaryItems.Full) return "FULL"; if (alias == MessageSummaryItems.Fast) return "FAST"; } var tokens = new List (); // now add on any additional summary items... if ((items & MessageSummaryItems.UniqueId) != 0) tokens.Add ("UID"); if ((items & MessageSummaryItems.Flags) != 0) tokens.Add ("FLAGS"); if ((items & MessageSummaryItems.InternalDate) != 0) tokens.Add ("INTERNALDATE"); if ((items & MessageSummaryItems.Size) != 0) tokens.Add ("RFC822.SIZE"); if ((items & MessageSummaryItems.Envelope) != 0) tokens.Add ("ENVELOPE"); if ((items & MessageSummaryItems.BodyStructure) != 0) tokens.Add ("BODYSTRUCTURE"); if ((items & MessageSummaryItems.Body) != 0) tokens.Add ("BODY"); if ((engine.Capabilities & ImapCapabilities.CondStore) != 0) { if ((items & MessageSummaryItems.ModSeq) != 0) tokens.Add ("MODSEQ"); } if ((engine.Capabilities & ImapCapabilities.Annotate) != 0) { if ((items & MessageSummaryItems.Annotations) != 0) tokens.Add ("ANNOTATION (/* (value size))"); } if ((engine.Capabilities & ImapCapabilities.ObjectID) != 0) { if ((items & MessageSummaryItems.EmailId) != 0) tokens.Add ("EMAILID"); if ((items & MessageSummaryItems.ThreadId) != 0) tokens.Add ("THREADID"); } if ((engine.Capabilities & ImapCapabilities.GMailExt1) != 0) { // now for the GMail extension items if ((items & MessageSummaryItems.GMailMessageId) != 0) tokens.Add ("X-GM-MSGID"); if ((items & MessageSummaryItems.GMailThreadId) != 0) tokens.Add ("X-GM-THRID"); if ((items & MessageSummaryItems.GMailLabels) != 0) tokens.Add ("X-GM-LABELS"); } if ((items & MessageSummaryItems.Headers) != 0) { tokens.Add ("BODY.PEEK[HEADER]"); } else if ((items & MessageSummaryItems.References) != 0 || headers.Count > 0) { var headerFields = new StringBuilder ("BODY.PEEK[HEADER.FIELDS ("); var references = false; foreach (var header in headers) { if (header.Equals ("REFERENCES", StringComparison.OrdinalIgnoreCase)) references = true; headerFields.Append (header); headerFields.Append (' '); } if ((items & MessageSummaryItems.References) != 0 && !references) headerFields.Append ("REFERENCES "); headerFields[headerFields.Length - 1] = ')'; headerFields.Append (']'); tokens.Add (headerFields.ToString ()); } if (tokens.Count == 1 && !isNotify) return tokens[0]; return string.Format ("({0})", string.Join (" ", tokens)); } string FormatSummaryItems (ref MessageSummaryItems items, HashSet headers, out bool previewText) { return FormatSummaryItems (Engine, ref items, headers, out previewText); } static IList AsReadOnly (ICollection collection) { var array = new IMessageSummary[collection.Count]; collection.CopyTo (array, 0); return new ReadOnlyCollection (array); } class FetchPreviewTextContext : FetchStreamContextBase { static readonly PlainTextPreviewer textPreviewer = new PlainTextPreviewer (); static readonly HtmlTextPreviewer htmlPreviewer = new HtmlTextPreviewer (); readonly FetchSummaryContext ctx; readonly ImapFolder folder; public FetchPreviewTextContext (ImapFolder folder, FetchSummaryContext ctx) : base (null) { this.folder = folder; this.ctx = ctx; } public override Task AddAsync (Section section, bool doAsync, CancellationToken cancellationToken) { MessageSummary message; if (!ctx.TryGetValue (section.Index, out message)) return Complete; var body = message.TextBody; TextPreviewer previewer; if (body == null) { previewer = htmlPreviewer; body = message.HtmlBody; } else { previewer = textPreviewer; } if (body == null) return Complete; var charset = body.ContentType.Charset ?? "utf-8"; ContentEncoding encoding; if (!string.IsNullOrEmpty (body.ContentTransferEncoding)) MimeUtils.TryParse (body.ContentTransferEncoding, out encoding); else encoding = ContentEncoding.Default; using (var memory = new MemoryStream ()) { var content = new MimeContent (section.Stream, encoding); content.DecodeTo (memory); memory.Position = 0; try { message.PreviewText = previewer.GetPreviewText (memory, charset); } catch (DecoderFallbackException) { memory.Position = 0; message.PreviewText = previewer.GetPreviewText (memory, ImapEngine.Latin1); } message.Fields |= MessageSummaryItems.PreviewText; folder.OnMessageSummaryFetched (message); } return Complete; } public override Task SetUniqueIdAsync (int index, UniqueId uid, bool doAsync, CancellationToken cancellationToken) { return Complete; } } async Task FetchPreviewTextAsync (FetchSummaryContext sctx, Dictionary bodies, int octets, bool doAsync, CancellationToken cancellationToken) { foreach (var pair in bodies) { var uids = pair.Value; string specifier; if (!string.IsNullOrEmpty (pair.Key)) specifier = pair.Key; else specifier = "TEXT"; // TODO: if the IMAP server supports the CONVERT extension, we could possibly use the // CONVERT command instead to decode *and* convert (html) into utf-8 plain text. // // e.g. "UID CONVERT {0} (\"text/plain\" (\"charset\" \"utf-8\")) BINARY[{1}]<0.{2}>\r\n" // // This would allow us to more accurately fetch X number of characters because we wouldn't // need to guestimate accounting for base64/quoted-printable decoding. var command = string.Format (CultureInfo.InvariantCulture, "UID FETCH {0} (BODY.PEEK[{1}]<0.{2}>)\r\n", uids, specifier, octets); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchPreviewTextContext (this, sctx); ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } finally { ctx.Dispose (); } } } async Task GetPreviewTextAsync (FetchSummaryContext sctx, bool doAsync, CancellationToken cancellationToken) { var textBodies = new Dictionary (); var htmlBodies = new Dictionary (); foreach (var item in sctx.Messages) { Dictionary bodies; var message = (MessageSummary) item; var body = message.TextBody; UniqueIdSet uids; if (body == null) { body = message.HtmlBody; bodies = htmlBodies; } else { bodies = textBodies; } if (body == null) { message.Fields |= MessageSummaryItems.PreviewText; message.PreviewText = string.Empty; OnMessageSummaryFetched (message); continue; } if (!bodies.TryGetValue (body.PartSpecifier, out uids)) { uids = new UniqueIdSet (SortOrder.Ascending); bodies.Add (body.PartSpecifier, uids); } uids.Add (message.UniqueId); } MessageExpunged += sctx.OnMessageExpunged; try { await FetchPreviewTextAsync (sctx, textBodies, PreviewTextLength, doAsync, cancellationToken).ConfigureAwait (false); await FetchPreviewTextAsync (sctx, htmlBodies, PreviewHtmlLength, doAsync, cancellationToken).ConfigureAwait (false); } finally { MessageExpunged -= sctx.OnMessageExpunged; } } async Task> FetchAsync (IList uids, MessageSummaryItems items, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (uids == null) throw new ArgumentNullException (nameof (uids)); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException (nameof (items)); CheckState (true, false); if (uids.Count == 0) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, EmptyHeaderFields, out previewText); var command = string.Format ("UID FETCH %s {0}\r\n", query); var ctx = new FetchSummaryContext (); MessageExpunged += ctx.OnMessageExpunged; try { foreach (var ic in Engine.CreateCommands (cancellationToken, this, command, uids)) { ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } } finally { MessageExpunged -= ctx.OnMessageExpunged; } if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (IList uids, MessageSummaryItems items, IEnumerable headers, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (uids == null) throw new ArgumentNullException (nameof (uids)); var headerFields = ImapUtils.GetUniqueHeaders (headers); CheckState (true, false); if (uids.Count == 0) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, headerFields, out previewText); var command = string.Format ("UID FETCH %s {0}\r\n", query); var ctx = new FetchSummaryContext (); MessageExpunged += ctx.OnMessageExpunged; try { foreach (var ic in Engine.CreateCommands (cancellationToken, this, command, uids)) { ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } } finally { MessageExpunged -= ctx.OnMessageExpunged; } if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (IList uids, ulong modseq, MessageSummaryItems items, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (uids == null) throw new ArgumentNullException (nameof (uids)); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException (nameof (items)); if (!supportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); if (uids.Count == 0) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, EmptyHeaderFields, out previewText); var vanished = Engine.QResyncEnabled ? " VANISHED" : string.Empty; var modseqValue = modseq.ToString (CultureInfo.InvariantCulture); var command = string.Format ("UID FETCH %s {0} (CHANGEDSINCE {1}{2})\r\n", query, modseqValue, vanished); var ctx = new FetchSummaryContext (); MessageExpunged += ctx.OnMessageExpunged; try { foreach (var ic in Engine.CreateCommands (cancellationToken, this, command, uids)) { ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } } finally { MessageExpunged -= ctx.OnMessageExpunged; } if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (IList uids, ulong modseq, MessageSummaryItems items, IEnumerable headers, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (uids == null) throw new ArgumentNullException (nameof (uids)); var headerFields = ImapUtils.GetUniqueHeaders (headers); if (!supportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); if (uids.Count == 0) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, headerFields, out previewText); var vanished = Engine.QResyncEnabled ? " VANISHED" : string.Empty; var modseqValue = modseq.ToString (CultureInfo.InvariantCulture); var command = string.Format ("UID FETCH %s {0} (CHANGEDSINCE {1}{2})\r\n", query, modseqValue, vanished); var ctx = new FetchSummaryContext (); MessageExpunged += ctx.OnMessageExpunged; try { foreach (var ic in Engine.CreateCommands (cancellationToken, this, command, uids)) { ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } } finally { MessageExpunged -= ctx.OnMessageExpunged; } if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } /// /// Fetches the message summaries for the specified message UIDs. /// /// /// Fetches the message summaries for the specified message UIDs. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// /// /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not currently open. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList uids, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, items, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message UIDs. /// /// /// Fetches the message summaries for the specified message UIDs. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// /// /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not currently open. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList uids, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, items, true, cancellationToken); } /// /// Fetches the message summaries for the specified message UIDs. /// /// /// Fetches the message summaries for the specified message UIDs. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList uids, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return Fetch (uids, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Asynchronously fetches the message summaries for the specified message UIDs. /// /// /// Fetches the message summaries for the specified message UIDs. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList uids, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Fetches the message summaries for the specified message UIDs. /// /// /// Fetches the message summaries for the specified message UIDs. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList uids, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, items, headers, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message UIDs. /// /// /// Fetches the message summaries for the specified message UIDs. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList uids, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, items, headers, true, cancellationToken); } /// /// Fetches the message summaries for the specified message UIDs that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message UIDs that /// have a higher mod-sequence value than the one specified. /// If the IMAP server supports the QRESYNC extension and the application has /// enabled this feature via , /// then this method will emit events for messages /// that have vanished since the specified mod-sequence value. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The mod-sequence value. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList uids, ulong modseq, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, modseq, items, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message UIDs that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message UIDs that /// have a higher mod-sequence value than the one specified. /// If the IMAP server supports the QRESYNC extension and the application has /// enabled this feature via , /// then this method will emit events for messages /// that have vanished since the specified mod-sequence value. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The mod-sequence value. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList uids, ulong modseq, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, modseq, items, true, cancellationToken); } /// /// Fetches the message summaries for the specified message UIDs that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message UIDs that /// have a higher mod-sequence value than the one specified. /// If the IMAP server supports the QRESYNC extension and the application has /// enabled this feature via , /// then this method will emit events for messages /// that have vanished since the specified mod-sequence value. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList uids, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return Fetch (uids, modseq, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Asynchronously fetches the message summaries for the specified message UIDs that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message UIDs that /// have a higher mod-sequence value than the one specified. /// If the IMAP server supports the QRESYNC extension and the application has /// enabled this feature via , /// then this method will emit events for messages /// that have vanished since the specified mod-sequence value. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList uids, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, modseq, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Fetches the message summaries for the specified message UIDs that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message UIDs that /// have a higher mod-sequence value than the one specified. /// If the IMAP server supports the QRESYNC extension and the application has /// enabled this feature via , /// then this method will emit events for messages /// that have vanished since the specified mod-sequence value. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList uids, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, modseq, items, headers, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message UIDs that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message UIDs that /// have a higher mod-sequence value than the one specified. /// If the IMAP server supports the QRESYNC extension and the application has /// enabled this feature via , /// then this method will emit events for messages /// that have vanished since the specified mod-sequence value. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The UIDs. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList uids, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (uids, modseq, items, headers, true, cancellationToken); } async Task> FetchAsync (IList indexes, MessageSummaryItems items, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (indexes == null) throw new ArgumentNullException (nameof (indexes)); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException (nameof (items)); CheckState (true, false); CheckAllowIndexes (); if (indexes.Count == 0) return new IMessageSummary[0]; var set = ImapUtils.FormatIndexSet (indexes); var query = FormatSummaryItems (ref items, EmptyHeaderFields, out previewText); var command = string.Format ("FETCH {0} {1}\r\n", set, query); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (IList indexes, MessageSummaryItems items, IEnumerable headers, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (indexes == null) throw new ArgumentNullException (nameof (indexes)); var headerFields = ImapUtils.GetUniqueHeaders (headers); CheckState (true, false); CheckAllowIndexes (); if (indexes.Count == 0) return new IMessageSummary[0]; var set = ImapUtils.FormatIndexSet (indexes); var query = FormatSummaryItems (ref items, headerFields, out previewText); var command = string.Format ("FETCH {0} {1}\r\n", set, query); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (IList indexes, ulong modseq, MessageSummaryItems items, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (indexes == null) throw new ArgumentNullException (nameof (indexes)); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException (nameof (items)); if (!supportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); CheckAllowIndexes (); if (indexes.Count == 0) return new IMessageSummary[0]; var set = ImapUtils.FormatIndexSet (indexes); var query = FormatSummaryItems (ref items, EmptyHeaderFields, out previewText); var modseqValue = modseq.ToString (CultureInfo.InvariantCulture); var command = string.Format ("FETCH {0} {1} (CHANGEDSINCE {2})\r\n", set, query, modseqValue); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (IList indexes, ulong modseq, MessageSummaryItems items, IEnumerable headers, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (indexes == null) throw new ArgumentNullException (nameof (indexes)); var headerFields = ImapUtils.GetUniqueHeaders (headers); if (!supportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); CheckAllowIndexes (); if (indexes.Count == 0) return new IMessageSummary[0]; var set = ImapUtils.FormatIndexSet (indexes); var query = FormatSummaryItems (ref items, headerFields, out previewText); var modseqValue = modseq.ToString (CultureInfo.InvariantCulture); var command = string.Format ("FETCH {0} {1} (CHANGEDSINCE {2})\r\n", set, query, modseqValue); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } /// /// Fetches the message summaries for the specified message indexes. /// /// /// Fetches the message summaries for the specified message indexes. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList indexes, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, items, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message indexes. /// /// /// Fetches the message summaries for the specified message indexes. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList indexes, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, items, true, cancellationToken); } /// /// Fetches the message summaries for the specified message indexes. /// /// /// Fetches the message summaries for the specified message indexes. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList indexes, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return Fetch (indexes, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Asynchronously fetches the message summaries for the specified message indexes. /// /// /// Fetches the message summaries for the specified message indexes. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList indexes, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Fetches the message summaries for the specified message indexes. /// /// /// Fetches the message summaries for the specified message indexes. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList indexes, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, items, headers, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message indexes. /// /// /// Fetches the message summaries for the specified message indexes. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList indexes, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, items, headers, true, cancellationToken); } /// /// Fetches the message summaries for the specified message indexes that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message indexes that /// have a higher mod-sequence value than the one specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The mod-sequence value. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList indexes, ulong modseq, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, modseq, items, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message indexes that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message indexes that /// have a higher mod-sequence value than the one specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The mod-sequence value. /// The message summary items to fetch. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList indexes, ulong modseq, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, modseq, items, true, cancellationToken); } /// /// Fetches the message summaries for the specified message indexes that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message indexes that /// have a higher mod-sequence value than the one specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList indexes, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return Fetch (indexes, modseq, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Asynchronously fetches the message summaries for the specified message indexes that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message indexes that /// have a higher mod-sequence value than the one specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList indexes, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, modseq, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Fetches the message summaries for the specified message indexes that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message indexes that /// have a higher mod-sequence value than the one specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (IList indexes, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, modseq, items, headers, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the specified message indexes that have a /// higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the specified message indexes that /// have a higher mod-sequence value than the one specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The indexes. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (IList indexes, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (indexes, modseq, items, headers, true, cancellationToken); } static string GetFetchRange (int min, int max) { var minValue = (min + 1).ToString (CultureInfo.InvariantCulture); if (min == max) return minValue; var maxValue = max != -1 ? (max + 1).ToString (CultureInfo.InvariantCulture) : "*"; return string.Format (CultureInfo.InvariantCulture, "{0}:{1}", minValue, maxValue); } async Task> FetchAsync (int min, int max, MessageSummaryItems items, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (min < 0) throw new ArgumentOutOfRangeException (nameof (min)); if (max != -1 && max < min) throw new ArgumentOutOfRangeException (nameof (max)); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException (nameof (items)); CheckState (true, false); CheckAllowIndexes (); if (min == Count) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, EmptyHeaderFields, out previewText); var command = string.Format ("FETCH {0} {1}\r\n", GetFetchRange (min, max), query); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (int min, int max, MessageSummaryItems items, IEnumerable headers, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (min < 0) throw new ArgumentOutOfRangeException (nameof (min)); if (max != -1 && max < min) throw new ArgumentOutOfRangeException (nameof (max)); var headerFields = ImapUtils.GetUniqueHeaders (headers); CheckState (true, false); CheckAllowIndexes (); if (min == Count) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, headerFields, out previewText); var command = string.Format ("FETCH {0} {1}\r\n", GetFetchRange (min, max), query); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (int min, int max, ulong modseq, MessageSummaryItems items, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (min < 0) throw new ArgumentOutOfRangeException (nameof (min)); if (max != -1 && max < min) throw new ArgumentOutOfRangeException (nameof (max)); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException (nameof (items)); if (!supportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); CheckAllowIndexes (); var query = FormatSummaryItems (ref items, EmptyHeaderFields, out previewText); var modseqValue = modseq.ToString (CultureInfo.InvariantCulture); var command = string.Format ("FETCH {0} {1} (CHANGEDSINCE {2})\r\n", GetFetchRange (min, max), query, modseqValue); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } async Task> FetchAsync (int min, int max, ulong modseq, MessageSummaryItems items, IEnumerable headers, bool doAsync, CancellationToken cancellationToken) { bool previewText; if (min < 0) throw new ArgumentOutOfRangeException (nameof (min)); if (max != -1 && max < min) throw new ArgumentOutOfRangeException (nameof (max)); var headerFields = ImapUtils.GetUniqueHeaders (headers); if (!supportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); CheckAllowIndexes (); var query = FormatSummaryItems (ref items, headerFields, out previewText); var modseqValue = modseq.ToString (CultureInfo.InvariantCulture); var command = string.Format ("FETCH {0} {1} (CHANGEDSINCE {2})\r\n", GetFetchRange (min, max), query, modseqValue); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItemsAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (previewText) await GetPreviewTextAsync (ctx, doAsync, cancellationToken).ConfigureAwait (false); return AsReadOnly (ctx.Messages); } /// /// Fetches the message summaries for the messages between the two indexes, inclusive. /// /// /// Fetches the message summaries for the messages between the two /// indexes, inclusive. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The message summary items to fetch. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// -or- /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (int min, int max, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, items, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the messages between the two indexes, inclusive. /// /// /// Fetches the message summaries for the messages between the two /// indexes, inclusive. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The message summary items to fetch. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// -or- /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (int min, int max, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, items, true, cancellationToken); } /// /// Fetches the message summaries for the messages between the two indexes, inclusive. /// /// /// Fetches the message summaries for the messages between the two /// indexes, inclusive. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (int min, int max, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return Fetch (min, max, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Asynchronously fetches the message summaries for the messages between the two indexes, inclusive. /// /// /// Fetches the message summaries for the messages between the two /// indexes, inclusive. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (int min, int max, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Fetches the message summaries for the messages between the two indexes, inclusive. /// /// /// Fetches the message summaries for the messages between the two /// indexes, inclusive. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (int min, int max, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, items, headers, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the messages between the two indexes, inclusive. /// /// /// Fetches the message summaries for the messages between the two /// indexes, inclusive. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (int min, int max, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, items, headers, true, cancellationToken); } /// /// Fetches the message summaries for the messages between the two indexes (inclusive) /// that have a higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the messages between the two /// indexes (inclusive) that have a higher mod-sequence value than the one /// specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The mod-sequence value. /// The message summary items to fetch. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// -or- /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (int min, int max, ulong modseq, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, modseq, items, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the messages between the two indexes (inclusive) /// that have a higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the messages between the two /// indexes (inclusive) that have a higher mod-sequence value than the one /// specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The mod-sequence value. /// The message summary items to fetch. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// -or- /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (int min, int max, ulong modseq, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, modseq, items, true, cancellationToken); } /// /// Fetches the message summaries for the messages between the two indexes (inclusive) /// that have a higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the messages between the two /// indexes (inclusive) that have a higher mod-sequence value than the one /// specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (int min, int max, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return Fetch (min, max, modseq, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Asynchronously fetches the message summaries for the messages between the two indexes (inclusive) /// that have a higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the messages between the two /// indexes (inclusive) that have a higher mod-sequence value than the one /// specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (int min, int max, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, modseq, items, ImapUtils.GetUniqueHeaders (headers), cancellationToken); } /// /// Fetches the message summaries for the messages between the two indexes (inclusive) /// that have a higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the messages between the two /// indexes (inclusive) that have a higher mod-sequence value than the one /// specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Fetch (int min, int max, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, modseq, items, headers, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously fetches the message summaries for the messages between the two indexes (inclusive) /// that have a higher mod-sequence value than the one specified. /// /// /// Fetches the message summaries for the messages between the two /// indexes (inclusive) that have a higher mod-sequence value than the one /// specified. /// It should be noted that if another client has modified any message /// in the folder, the IMAP server may choose to return information that was /// not explicitly requested. It is therefore important to be prepared to /// handle both additional fields on a for /// messages that were requested as well as summaries for messages that were /// not requested at all. /// /// An enumeration of summaries for the requested messages. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// The mod-sequence value. /// The message summary items to fetch. /// The desired header fields. /// The cancellation token. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// One or more of the specified is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The does not support mod-sequences. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> FetchAsync (int min, int max, ulong modseq, MessageSummaryItems items, IEnumerable headers, CancellationToken cancellationToken = default (CancellationToken)) { return FetchAsync (min, max, modseq, items, headers, true, cancellationToken); } /// /// Create a backing stream for use with the GetMessage, GetBodyPart, and GetStream methods. /// /// /// Allows subclass implementations to override the type of stream /// created for use with the GetMessage, GetBodyPart and GetStream methods. /// This could be useful for subclass implementations that intend to implement /// support for caching and/or for subclass implementations that want to use /// temporary file streams instead of memory-based streams for larger amounts of /// message data. /// Subclasses that implement caching using this API should wait for /// before adding the stream to their cache. /// Streams returned by this method SHOULD clean up any allocated resources /// such as deleting temporary files from the file system. /// The will not be available for the various /// GetMessage(), GetBodyPart() and GetStream() methods that take a message index rather /// than a . It may also not be available if the IMAP server /// response does not specify the UID value prior to sending the literal-string /// token containing the message stream. /// /// /// The stream. /// The unique identifier of the message, if available. /// The section of the message that is being fetched. /// The starting offset of the message section being fetched. /// The length of the stream being fetched, measured in bytes. protected virtual Stream CreateStream (UniqueId? uid, string section, int offset, int length) { if (length > 4096) return new MemoryBlockStream (); return new MemoryStream (length); } /// /// Commit a stream returned by . /// /// /// Commits a stream returned by . /// This method is called only after both the message data has successfully /// been written to the stream returned by and a /// has been obtained for the associated message. /// For subclasses implementing caching, this method should be used for /// committing the stream to their cache. /// Subclass implementations may take advantage of the fact that /// allows returning a new /// reference if they move a file on the file system and wish to return a new /// based on the new path, for example. /// /// /// The stream. /// The stream. /// The unique identifier of the message. /// The section of the message that the stream represents. /// The starting offset of the message section. /// The length of the stream, measured in bytes. protected virtual Stream CommitStream (Stream stream, UniqueId uid, string section, int offset, int length) { return stream; } async Task ParseHeadersAsync (Stream stream, bool doAsync, CancellationToken cancellationToken) { try { return await Engine.ParseHeadersAsync (stream, doAsync, cancellationToken).ConfigureAwait (false); } finally { stream.Dispose (); } } async Task ParseMessageAsync (Stream stream, bool doAsync, CancellationToken cancellationToken) { bool dispose = !(stream is MemoryStream || stream is MemoryBlockStream); try { return await Engine.ParseMessageAsync (stream, !dispose, doAsync, cancellationToken).ConfigureAwait (false); } finally { if (dispose) stream.Dispose (); } } async Task ParseEntityAsync (Stream stream, bool dispose, bool doAsync, CancellationToken cancellationToken) { try { return await Engine.ParseEntityAsync (stream, !dispose, doAsync, cancellationToken).ConfigureAwait (false); } finally { if (dispose) stream.Dispose (); } } class Section { public int Index; public UniqueId? UniqueId; public Stream Stream; public string Name; public int Offset; public int Length; public Section (Stream stream, int index, UniqueId? uid, string name, int offset, int length) { Stream = stream; Offset = offset; Length = length; UniqueId = uid; Index = index; Name = name; } } abstract class FetchStreamContextBase : IDisposable { protected static readonly Task Complete = Task.FromResult (true); public readonly List
Sections = new List
(); readonly ITransferProgress progress; public FetchStreamContextBase (ITransferProgress progress) { this.progress = progress; } public abstract Task AddAsync (Section section, bool doAsync, CancellationToken cancellationToken); public virtual bool Contains (int index, string specifier, out Section section) { section = null; return false; } public abstract Task SetUniqueIdAsync (int index, UniqueId uid, bool doAsync, CancellationToken cancellationToken); public void Report (long nread, long total) { if (progress == null) return; progress.Report (nread, total); } public void Dispose () { for (int i = 0; i < Sections.Count; i++) { var section = Sections[i]; try { section.Stream.Dispose (); } catch (IOException) { } } } } class FetchStreamContext : FetchStreamContextBase { public FetchStreamContext (ITransferProgress progress) : base (progress) { } public override Task AddAsync (Section section, bool doAsync, CancellationToken cancellationToken) { Sections.Add (section); return Complete; } public bool TryGetSection (UniqueId uid, string specifier, out Section section, bool remove = false) { for (int i = 0; i < Sections.Count; i++) { var item = Sections[i]; if (!item.UniqueId.HasValue || item.UniqueId.Value != uid) continue; if (item.Name.Equals (specifier, StringComparison.OrdinalIgnoreCase)) { if (remove) Sections.RemoveAt (i); section = item; return true; } } section = null; return false; } public bool TryGetSection (int index, string specifier, out Section section, bool remove = false) { for (int i = 0; i < Sections.Count; i++) { var item = Sections[i]; if (item.Index != index) continue; if (item.Name.Equals (specifier, StringComparison.OrdinalIgnoreCase)) { if (remove) Sections.RemoveAt (i); section = item; return true; } } section = null; return false; } public override Task SetUniqueIdAsync (int index, UniqueId uid, bool doAsync, CancellationToken cancellationToken) { for (int i = 0; i < Sections.Count; i++) { if (Sections[i].Index == index) Sections[i].UniqueId = uid; } return Complete; } } async Task FetchStreamAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { var token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); var annotations = new AnnotationsChangedEventArgs (index); var labels = new MessageLabelsChangedEventArgs (index); var flags = new MessageFlagsChangedEventArgs (index); var modSeq = new ModSeqChangedEventArgs (index); var ctx = (FetchStreamContextBase) ic.UserData; var sectionBuilder = new StringBuilder (); bool annotationsChanged = false; bool modSeqChanged = false; bool labelsChanged = false; bool flagsChanged = false; var buf = new byte[4096]; long nread = 0, size = 0; UniqueId? uid = null; Section section; Stream stream; string name; int n; ImapEngine.AssertToken (token, ImapTokenType.OpenParen, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); do { token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.Eoln) { // Note: Most likely the the message body was calculated to be 1 or 2 bytes too // short (e.g. did not include the trailing ) and that is the EOLN we just // reached. Ignore it and continue as normal. // // See https://github.com/jstedfast/MailKit/issues/954 for details. token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); } if (token.Type == ImapTokenType.CloseParen) break; ImapEngine.AssertToken (token, ImapTokenType.Atom, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var atom = (string) token.Value; int offset = 0, length; ulong modseq; uint value; switch (atom.ToUpperInvariant ()) { case "BODY": token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.OpenBracket, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); sectionBuilder.Clear (); do { token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { sectionBuilder.Append (" ("); do { token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.CloseParen) break; // the header field names will generally be atoms or qstrings but may also be literals switch (token.Type) { case ImapTokenType.Literal: sectionBuilder.Append (await engine.ReadLiteralAsync (doAsync, ic.CancellationToken).ConfigureAwait (false)); break; case ImapTokenType.QString: case ImapTokenType.Atom: sectionBuilder.Append ((string) token.Value); break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } sectionBuilder.Append (' '); } while (true); if (sectionBuilder[sectionBuilder.Length - 1] == ' ') sectionBuilder.Length--; sectionBuilder.Append (')'); } else { ImapEngine.AssertToken (token, ImapTokenType.Atom, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); sectionBuilder.Append ((string) token.Value); } } while (true); ImapEngine.AssertToken (token, ImapTokenType.CloseBracket, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.Atom) { // this might be a region ("<###>") var expr = (string) token.Value; if (expr.Length > 2 && expr[0] == '<' && expr[expr.Length - 1] == '>') { var region = expr.Substring (1, expr.Length - 2); int.TryParse (region, NumberStyles.None, CultureInfo.InvariantCulture, out offset); token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); } } name = sectionBuilder.ToString (); switch (token.Type) { case ImapTokenType.Literal: length = (int) token.Value; size += length; stream = CreateStream (uid, name, offset, length); try { do { if (doAsync) n = await engine.Stream.ReadAsync (buf, 0, buf.Length, ic.CancellationToken).ConfigureAwait (false); else n = engine.Stream.Read (buf, 0, buf.Length, ic.CancellationToken); if (n > 0) { stream.Write (buf, 0, n); nread += n; ctx.Report (nread, size); } else { break; } } while (true); stream.Position = 0; } catch { stream.Dispose (); throw; } break; case ImapTokenType.QString: case ImapTokenType.Atom: var buffer = Encoding.UTF8.GetBytes ((string) token.Value); length = buffer.Length; nread += length; size += length; stream = CreateStream (uid, name, offset, length); try { stream.Write (buffer, 0, length); ctx.Report (nread, size); stream.Position = 0; } catch { stream.Dispose (); throw; } break; case ImapTokenType.Nil: stream = CreateStream (uid, name, offset, 0); length = 0; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } if (uid.HasValue) stream = CommitStream (stream, uid.Value, name, offset, length); // prevent leaks in the (invalid) case where a section may be returned twice if (ctx.Contains (index, name, out section)) section.Stream.Dispose (); section = new Section (stream, index, uid, name, offset, length); await ctx.AddAsync (section, doAsync, ic.CancellationToken).ConfigureAwait (false); break; case "UID": token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); value = ImapEngine.ParseNumber (token, true, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); uid = new UniqueId (UidValidity, value); await ctx.SetUniqueIdAsync (index, uid.Value, doAsync, ic.CancellationToken).ConfigureAwait (false); annotations.UniqueId = uid.Value; modSeq.UniqueId = uid.Value; labels.UniqueId = uid.Value; flags.UniqueId = uid.Value; break; case "MODSEQ": token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.OpenParen, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); modseq = ImapEngine.ParseNumber64 (token, false, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.CloseParen, ImapEngine.GenericItemSyntaxErrorFormat, atom, token); if (modseq > HighestModSeq) UpdateHighestModSeq (modseq); annotations.ModSeq = modseq; modSeq.ModSeq = modseq; labels.ModSeq = modseq; flags.ModSeq = modseq; modSeqChanged = true; break; case "FLAGS": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message flags. flags.Flags = await ImapUtils.ParseFlagsListAsync (engine, atom, flags.Keywords, doAsync, ic.CancellationToken).ConfigureAwait (false); flagsChanged = true; break; case "X-GM-LABELS": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message labels. labels.Labels = await ImapUtils.ParseLabelsListAsync (engine, doAsync, ic.CancellationToken).ConfigureAwait (false); labelsChanged = true; break; case "ANNOTATION": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message annotations. annotations.Annotations = await ImapUtils.ParseAnnotationsAsync (engine, doAsync, ic.CancellationToken).ConfigureAwait (false); annotationsChanged = true; break; default: // Unexpected or unknown token (such as XAOL.SPAM.REASON or XAOL-MSGID). Simply read 1 more token (the argument) and ignore. token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.OpenParen) await SkipParenthesizedList (engine, doAsync, ic.CancellationToken).ConfigureAwait (false); break; } } while (true); ImapEngine.AssertToken (token, ImapTokenType.CloseParen, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); if (flagsChanged) OnMessageFlagsChanged (flags); if (labelsChanged) OnMessageLabelsChanged (labels); if (annotationsChanged) OnAnnotationsChanged (annotations); if (modSeqChanged) OnModSeqChanged (modSeq); } static string GetBodyPartQuery (string partSpec, bool headersOnly, out string[] tags) { string query; if (headersOnly) { tags = new string[1]; if (partSpec.Length > 0) { query = string.Format ("BODY.PEEK[{0}.MIME]", partSpec); tags[0] = partSpec + ".MIME"; } else { query = "BODY.PEEK[HEADER]"; tags[0] = "HEADER"; } } else { tags = new string[2]; if (partSpec.Length > 0) { tags[0] = partSpec + ".MIME"; tags[1] = partSpec; } else { tags[0] = "HEADER"; tags[1] = "TEXT"; } query = string.Format ("BODY.PEEK[{0}] BODY.PEEK[{1}]", tags[0], tags[1]); } return query; } async Task GetHeadersAsync (UniqueId uid, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); CheckState (true, false); var ic = new ImapCommand (Engine, cancellationToken, this, "UID FETCH %u (BODY.PEEK[HEADER])\r\n", uid.Id); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (uid, "HEADER", out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested message headers."); } finally { ctx.Dispose (); } return await ParseHeadersAsync (section.Stream, doAsync, cancellationToken).ConfigureAwait (false); } async Task GetHeadersAsync (UniqueId uid, string partSpecifier, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (partSpecifier == null) throw new ArgumentNullException (nameof (partSpecifier)); CheckState (true, false); string[] tags; var command = string.Format ("UID FETCH {0} ({1})\r\n", uid, GetBodyPartQuery (partSpecifier, true, out tags)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (uid, tags[0], out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested body part headers."); } finally { ctx.Dispose (); } return await ParseHeadersAsync (section.Stream, doAsync, cancellationToken).ConfigureAwait (false); } /// /// Get the specified message headers. /// /// /// Gets the specified message headers. /// /// The message headers. /// The UID of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override HeaderList GetHeaders (UniqueId uid, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (uid, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified message headers. /// /// /// Gets the specified message headers. /// /// The message headers. /// The UID of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetHeadersAsync (UniqueId uid, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (uid, true, cancellationToken, progress); } /// /// Get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The UID of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual HeaderList GetHeaders (UniqueId uid, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (uid, partSpecifier, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The UID of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual Task GetHeadersAsync (UniqueId uid, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (uid, partSpecifier, true, cancellationToken, progress); } /// /// Get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The UID of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override HeaderList GetHeaders (UniqueId uid, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetHeaders (uid, part.PartSpecifier, cancellationToken, progress); } /// /// Asynchronously get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The UID of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetHeadersAsync (UniqueId uid, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetHeadersAsync (uid, part.PartSpecifier, cancellationToken, progress); } async Task GetHeadersAsync (int index, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); CheckState (true, false); var ic = new ImapCommand (Engine, cancellationToken, this, "FETCH %d (BODY.PEEK[HEADER])\r\n", index + 1); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (index, "HEADER", out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested message."); } finally { ctx.Dispose (); } return await ParseHeadersAsync (section.Stream, doAsync, cancellationToken).ConfigureAwait (false); } async Task GetHeadersAsync (int index, string partSpecifier, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (partSpecifier == null) throw new ArgumentNullException (nameof (partSpecifier)); CheckState (true, false); string[] tags; var seqid = (index + 1).ToString (CultureInfo.InvariantCulture); var command = string.Format ("FETCH {0} ({1})\r\n", seqid, GetBodyPartQuery (partSpecifier, true, out tags)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (index, tags[0], out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested body part headers."); } finally { ctx.Dispose (); } return await ParseHeadersAsync (section.Stream, doAsync, cancellationToken).ConfigureAwait (false); } /// /// Get the specified message headers. /// /// /// Gets the specified message headers. /// /// The message headers. /// The index of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override HeaderList GetHeaders (int index, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (index, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified message headers. /// /// /// Gets the specified message headers. /// /// The message headers. /// The index of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetHeadersAsync (int index, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (index, true, cancellationToken, progress); } /// /// Get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The index of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual HeaderList GetHeaders (int index, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (index, partSpecifier, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The index of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual Task GetHeadersAsync (int index, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetHeadersAsync (index, partSpecifier, true, cancellationToken, progress); } /// /// Get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The index of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override HeaderList GetHeaders (int index, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetHeaders (index, part.PartSpecifier, cancellationToken, progress); } /// /// Asynchronously get the specified body part headers. /// /// /// Gets the specified body part headers. /// /// The body part headers. /// The index of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested body part headers. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetHeadersAsync (int index, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetHeadersAsync (index, part.PartSpecifier, cancellationToken, progress); } async Task GetMessageAsync (UniqueId uid, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); CheckState (true, false); var ic = new ImapCommand (Engine, cancellationToken, this, "UID FETCH %u (BODY.PEEK[])\r\n", uid.Id); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (uid, string.Empty, out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested message."); } finally { ctx.Dispose (); } return await ParseMessageAsync (section.Stream, doAsync, cancellationToken).ConfigureAwait (false); } /// /// Get the specified message. /// /// /// Gets the specified message. /// /// The message. /// The UID of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override MimeMessage GetMessage (UniqueId uid, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetMessageAsync (uid, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified message. /// /// /// Gets the specified message. /// /// The message. /// The UID of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetMessageAsync (UniqueId uid, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetMessageAsync (uid, true, cancellationToken, progress); } async Task GetMessageAsync (int index, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); CheckState (true, false); var ic = new ImapCommand (Engine, cancellationToken, this, "FETCH %d (BODY.PEEK[])\r\n", index + 1); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (index, string.Empty, out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested message."); } finally { ctx.Dispose (); } return await ParseMessageAsync (section.Stream, doAsync, cancellationToken).ConfigureAwait (false); } /// /// Get the specified message. /// /// /// Gets the specified message. /// /// The message. /// The index of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override MimeMessage GetMessage (int index, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetMessageAsync (index, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified message. /// /// /// Gets the specified message. /// /// The message. /// The index of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetMessageAsync (int index, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetMessageAsync (index, true, cancellationToken, progress); } async Task GetBodyPartAsync (UniqueId uid, string partSpecifier, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (partSpecifier == null) throw new ArgumentNullException (nameof (partSpecifier)); CheckState (true, false); string[] tags; var command = string.Format ("UID FETCH {0} ({1})\r\n", uid, GetBodyPartQuery (partSpecifier, false, out tags)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); ChainedStream chained = null; bool dispose = false; Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); chained = new ChainedStream (); foreach (var tag in tags) { if (!ctx.TryGetSection (uid, tag, out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested body part."); if (!(section.Stream is MemoryStream || section.Stream is MemoryBlockStream)) dispose = true; chained.Add (section.Stream); } } catch { if (chained != null) chained.Dispose (); throw; } finally { ctx.Dispose (); } var entity = await ParseEntityAsync (chained, dispose, doAsync, cancellationToken).ConfigureAwait (false); if (partSpecifier.Length == 0) { for (int i = entity.Headers.Count; i > 0; i--) { var header = entity.Headers[i - 1]; if (!header.Field.StartsWith ("Content-", StringComparison.OrdinalIgnoreCase)) entity.Headers.RemoveAt (i - 1); } } return entity; } /// /// Get the specified body part. /// /// /// Gets the specified body part. /// /// The body part. /// The UID of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message body. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual MimeEntity GetBodyPart (UniqueId uid, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetBodyPartAsync (uid, partSpecifier, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified body part. /// /// /// Gets the specified body part. /// /// The body part. /// The UID of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message body. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual Task GetBodyPartAsync (UniqueId uid, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetBodyPartAsync (uid, partSpecifier, true, cancellationToken, progress); } /// /// Get the specified body part. /// /// /// Gets the specified body part. /// /// /// /// /// The body part. /// The UID of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message body. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override MimeEntity GetBodyPart (UniqueId uid, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetBodyPart (uid, part.PartSpecifier, cancellationToken, progress); } /// /// Asynchronously get the specified body part. /// /// /// Gets the specified body part. /// /// /// /// /// The body part. /// The UID of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message body. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetBodyPartAsync (UniqueId uid, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetBodyPartAsync (uid, part.PartSpecifier, cancellationToken, progress); } async Task GetBodyPartAsync (int index, string partSpecifier, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (partSpecifier == null) throw new ArgumentNullException (nameof (partSpecifier)); CheckState (true, false); string[] tags; var seqid = (index + 1).ToString (CultureInfo.InvariantCulture); var command = string.Format ("FETCH {0} ({1})\r\n", seqid, GetBodyPartQuery (partSpecifier, false, out tags)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); ChainedStream chained = null; bool dispose = false; Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); chained = new ChainedStream (); foreach (var tag in tags) { if (!ctx.TryGetSection (index, tag, out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested body part."); if (!(section.Stream is MemoryStream || section.Stream is MemoryBlockStream)) dispose = true; chained.Add (section.Stream); } } catch { if (chained != null) chained.Dispose (); throw; } finally { ctx.Dispose (); } var entity = await ParseEntityAsync (chained, dispose, doAsync, cancellationToken).ConfigureAwait (false); if (partSpecifier.Length == 0) { for (int i = entity.Headers.Count; i > 0; i--) { var header = entity.Headers[i - 1]; if (!header.Field.StartsWith ("Content-", StringComparison.OrdinalIgnoreCase)) entity.Headers.RemoveAt (i - 1); } } return entity; } /// /// Get the specified body part. /// /// /// Gets the specified body part. /// /// The body part. /// The index of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual MimeEntity GetBodyPart (int index, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetBodyPartAsync (index, partSpecifier, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified body part. /// /// /// Gets the specified body part. /// /// The body part. /// The index of the message. /// The body part specifier. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual Task GetBodyPartAsync (int index, string partSpecifier, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetBodyPartAsync (index, partSpecifier, true, cancellationToken, progress); } /// /// Get the specified body part. /// /// /// Gets the specified body part. /// /// The body part. /// The index of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override MimeEntity GetBodyPart (int index, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetBodyPart (index, part.PartSpecifier, cancellationToken, progress); } /// /// Asynchronously get the specified body part. /// /// /// Gets the specified body part. /// /// The body part. /// The index of the message. /// The body part. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetBodyPartAsync (int index, BodyPart part, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (part == null) throw new ArgumentNullException (nameof (part)); return GetBodyPartAsync (index, part.PartSpecifier, cancellationToken, progress); } async Task GetStreamAsync (UniqueId uid, int offset, int count, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (offset < 0) throw new ArgumentOutOfRangeException (nameof (offset)); if (count < 0) throw new ArgumentOutOfRangeException (nameof (count)); CheckState (true, false); if (count == 0) return new MemoryStream (); var ic = new ImapCommand (Engine, cancellationToken, this, "UID FETCH %u (BODY.PEEK[]<%d.%d>)\r\n", uid.Id, offset, count); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (uid, string.Empty, out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); } finally { ctx.Dispose (); } return section.Stream; } async Task GetStreamAsync (int index, int offset, int count, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (offset < 0) throw new ArgumentOutOfRangeException (nameof (offset)); if (count < 0) throw new ArgumentOutOfRangeException (nameof (count)); CheckState (true, false); if (count == 0) return new MemoryStream (); var ic = new ImapCommand (Engine, cancellationToken, this, "FETCH %d (BODY.PEEK[]<%d.%d>)\r\n", index + 1, offset, count); var ctx = new FetchStreamContext (progress); Section section; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (index, string.Empty, out section, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); } finally { ctx.Dispose (); } return section.Stream; } /// /// Get a substream of the specified message. /// /// /// Fetches a substream of the message. If the starting offset is beyond /// the end of the message, an empty stream is returned. If the number of /// bytes desired extends beyond the end of the message, a truncated stream /// will be returned. /// /// The stream. /// The UID of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Stream GetStream (UniqueId uid, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (uid, offset, count, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously gets a substream of the specified message. /// /// /// Fetches a substream of the message. If the starting offset is beyond /// the end of the message, an empty stream is returned. If the number of /// bytes desired extends beyond the end of the message, a truncated stream /// will be returned. /// /// The stream. /// The UID of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetStreamAsync (UniqueId uid, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (uid, offset, count, true, cancellationToken, progress); } /// /// Get a substream of the specified message. /// /// /// Fetches a substream of the message. If the starting offset is beyond /// the end of the message, an empty stream is returned. If the number of /// bytes desired extends beyond the end of the message, a truncated stream /// will be returned. /// /// The stream. /// The index of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// -or- /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Stream GetStream (int index, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (index, offset, count, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously gets a substream of the specified message. /// /// /// Fetches a substream of the message. If the starting offset is beyond /// the end of the message, an empty stream is returned. If the number of /// bytes desired extends beyond the end of the message, a truncated stream /// will be returned. /// /// The stream. /// The index of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// -or- /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetStreamAsync (int index, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (index, offset, count, true, cancellationToken, progress); } async Task GetStreamAsync (UniqueId uid, string section, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (section == null) throw new ArgumentNullException (nameof (section)); CheckState (true, false); var command = string.Format ("UID FETCH {0} (BODY.PEEK[{1}])\r\n", uid, section); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Section s; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (uid, section, out s, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); } finally { ctx.Dispose (); } return s.Stream; } /// /// Get a substream of the specified body part. /// /// /// Gets a substream of the specified message. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The UID of the message. /// The desired section of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Stream GetStream (UniqueId uid, string section, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (uid, section, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously gets a substream of the specified body part. /// /// /// Gets a substream of the specified message. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The UID of the message. /// The desired section of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetStreamAsync (UniqueId uid, string section, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (uid, section, true, cancellationToken, progress); } async Task GetStreamAsync (UniqueId uid, string section, int offset, int count, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (section == null) throw new ArgumentNullException (nameof (section)); if (offset < 0) throw new ArgumentOutOfRangeException (nameof (offset)); if (count < 0) throw new ArgumentOutOfRangeException (nameof (count)); CheckState (true, false); if (count == 0) return new MemoryStream (); var range = string.Format (CultureInfo.InvariantCulture, "{0}.{1}", offset, count); var command = string.Format ("UID FETCH {0} (BODY.PEEK[{1}]<{2}>)\r\n", uid, section, range); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Section s; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (uid, section, out s, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); } finally { ctx.Dispose (); } return s.Stream; } /// /// Get a substream of the specified message. /// /// /// Gets a substream of the specified message. If the starting offset is beyond /// the end of the specified section of the message, an empty stream is returned. If /// the number of bytes desired extends beyond the end of the section, a truncated /// stream will be returned. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The UID of the message. /// The desired section of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// is null. /// /// /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Stream GetStream (UniqueId uid, string section, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (uid, section, offset, count, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously gets a substream of the specified message. /// /// /// Gets a substream of the specified message. If the starting offset is beyond /// the end of the specified section of the message, an empty stream is returned. If /// the number of bytes desired extends beyond the end of the section, a truncated /// stream will be returned. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The UID of the message. /// The desired section of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is invalid. /// /// /// is null. /// /// /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetStreamAsync (UniqueId uid, string section, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (uid, section, offset, count, true, cancellationToken, progress); } async Task GetStreamAsync (int index, string section, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (section == null) throw new ArgumentNullException (nameof (section)); CheckState (true, false); var seqid = (index + 1).ToString (CultureInfo.InvariantCulture); var command = string.Format ("FETCH {0} (BODY.PEEK[{1}])\r\n", seqid, section); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Section s; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (index, section, out s, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); } finally { ctx.Dispose (); } return s.Stream; } /// /// Get a substream of the specified message. /// /// /// Gets a substream of the specified message. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The index of the message. /// The desired section of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Stream GetStream (int index, string section, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (index, section, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously gets a substream of the specified message. /// /// /// Gets a substream of the specified message. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The index of the message. /// The desired section of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetStreamAsync (int index, string section, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (index, section, true, cancellationToken, progress); } async Task GetStreamAsync (int index, string section, int offset, int count, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (section == null) throw new ArgumentNullException (nameof (section)); if (offset < 0) throw new ArgumentOutOfRangeException (nameof (offset)); if (count < 0) throw new ArgumentOutOfRangeException (nameof (count)); CheckState (true, false); if (count == 0) return new MemoryStream (); var seqid = (index + 1).ToString (CultureInfo.InvariantCulture); var range = string.Format (CultureInfo.InvariantCulture, "{0}.{1}", offset, count); var command = string.Format ("FETCH {0} (BODY.PEEK[{1}]<{2}>)\r\n", seqid, section, range); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Section s; ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.TryGetSection (index, section, out s, true)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); } finally { ctx.Dispose (); } return s.Stream; } /// /// Get a substream of the specified message. /// /// /// Gets a substream of the specified message. If the starting offset is beyond /// the end of the specified section of the message, an empty stream is returned. If /// the number of bytes desired extends beyond the end of the section, a truncated /// stream will be returned. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The index of the message. /// The desired section of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// -or- /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Stream GetStream (int index, string section, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (index, section, offset, count, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously gets a substream of the specified message. /// /// /// Gets a substream of the specified message. If the starting offset is beyond /// the end of the specified section of the message, an empty stream is returned. If /// the number of bytes desired extends beyond the end of the section, a truncated /// stream will be returned. /// For more information about how to construct the , /// see Section 6.4.5 of RFC3501. /// /// The stream. /// The index of the message. /// The desired section of the message. /// The starting offset of the first desired byte. /// The number of bytes desired. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// /// /// is out of range. /// -or- /// is negative. /// -or- /// is negative. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The IMAP server did not return the requested message stream. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetStreamAsync (int index, string section, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamAsync (index, section, offset, count, true, cancellationToken, progress); } class FetchStreamCallbackContext : FetchStreamContextBase { readonly ImapFolder folder; readonly object callback; public FetchStreamCallbackContext (ImapFolder folder, object callback, ITransferProgress progress) : base (progress) { this.folder = folder; this.callback = callback; } Task InvokeCallbackAsync (ImapFolder folder, int index, UniqueId uid, Stream stream, bool doAsync, CancellationToken cancellationToken) { if (doAsync) return ((ImapFetchStreamAsyncCallback) callback) (folder, index, uid, stream, cancellationToken); ((ImapFetchStreamCallback) callback) (folder, index, uid, stream); return Complete; } public override async Task AddAsync (Section section, bool doAsync, CancellationToken cancellationToken) { if (section.UniqueId.HasValue) { await InvokeCallbackAsync (folder, section.Index, section.UniqueId.Value, section.Stream, doAsync, cancellationToken).ConfigureAwait (false); section.Stream.Dispose (); } else { Sections.Add (section); } } public override async Task SetUniqueIdAsync (int index, UniqueId uid, bool doAsync, CancellationToken cancellationToken) { for (int i = 0; i < Sections.Count; i++) { if (Sections[i].Index == index) { await InvokeCallbackAsync (folder, index, uid, Sections[i].Stream, doAsync, cancellationToken).ConfigureAwait (false); Sections[i].Stream.Dispose (); Sections.RemoveAt (i); break; } } } } async Task GetStreamsAsync (IList uids, object callback, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (uids == null) throw new ArgumentNullException (nameof (uids)); if (callback == null) throw new ArgumentNullException (nameof (callback)); CheckState (true, false); if (uids.Count == 0) return; var ctx = new FetchStreamCallbackContext (this, callback, progress); var command = "UID FETCH %s (BODY.PEEK[])\r\n"; try { foreach (var ic in Engine.CreateCommands (cancellationToken, this, command, uids)) { ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } } finally { ctx.Dispose (); } } async Task GetStreamsAsync (IList indexes, object callback, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (indexes == null) throw new ArgumentNullException (nameof (indexes)); if (callback == null) throw new ArgumentNullException (nameof (callback)); CheckState (true, false); CheckAllowIndexes (); if (indexes.Count == 0) return; var set = ImapUtils.FormatIndexSet (indexes); var command = string.Format ("FETCH {0} (UID BODY.PEEK[])\r\n", set); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamCallbackContext (this, callback, progress); ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } finally { ctx.Dispose (); } } async Task GetStreamsAsync (int min, int max, object callback, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (min < 0) throw new ArgumentOutOfRangeException (nameof (min)); if (max != -1 && max < min) throw new ArgumentOutOfRangeException (nameof (max)); if (callback == null) throw new ArgumentNullException (nameof (callback)); CheckState (true, false); CheckAllowIndexes (); if (min == Count) return; var command = string.Format ("FETCH {0} (UID BODY.PEEK[])\r\n", GetFetchRange (min, max)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamCallbackContext (this, callback, progress); ic.RegisterUntaggedHandler ("FETCH", FetchStreamAsync); ic.UserData = ctx; Engine.QueueCommand (ic); try { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); } finally { ctx.Dispose (); } } /// /// Get the streams for the specified messages. /// /// /// Gets the streams for the specified messages. /// /// The uids of the messages. /// /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual void GetStreams (IList uids, ImapFetchStreamCallback callback, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { GetStreamsAsync (uids, callback, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the streams for the specified messages. /// /// /// Asynchronously gets the streams for the specified messages. /// /// An awaitable task. /// The uids of the messages. /// /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual Task GetStreamsAsync (IList uids, ImapFetchStreamAsyncCallback callback, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamsAsync (uids, callback, true, cancellationToken, progress); } /// /// Get the streams for the specified messages. /// /// /// Gets the streams for the specified messages. /// /// The indexes of the messages. /// /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual void GetStreams (IList indexes, ImapFetchStreamCallback callback, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { GetStreamsAsync (indexes, callback, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the streams for the specified messages. /// /// /// Asynchronously gets the streams for the specified messages. /// /// An awaitable task. /// The indexes of the messages. /// /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual Task GetStreamsAsync (IList indexes, ImapFetchStreamAsyncCallback callback, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamsAsync (indexes, callback, true, cancellationToken, progress); } /// /// Get the streams for the specified messages. /// /// /// Gets the streams for the specified messages. /// /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual void GetStreams (int min, int max, ImapFetchStreamCallback callback, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { GetStreamsAsync (min, max, callback, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously get the streams for the specified messages. /// /// /// Asynchronously gets the streams for the specified messages. /// /// An awaitable task. /// The minimum index. /// The maximum index, or -1 to specify no upper bound. /// /// The cancellation token. /// The progress reporting mechanism. /// /// is out of range. /// -or- /// is out of range. /// /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public virtual Task GetStreamsAsync (int min, int max, ImapFetchStreamAsyncCallback callback, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return GetStreamsAsync (min, max, callback, true, cancellationToken, progress); } } }