/// <summary> /// Create a new <see cref="ImapCommandException"/> based on the specified command name and <see cref="ImapCommand"/> state. /// </summary> /// <remarks> /// Create a new <see cref="ImapCommandException"/> based on the specified command name and <see cref="ImapCommand"/> state. /// </remarks> /// <returns>A new command exception.</returns> /// <param name="command">The command name.</param> /// <param name="ic">The command state.</param> internal static ImapCommandException Create(string command, ImapCommand ic) { var result = ic.Response.ToString().ToUpperInvariant(); string message, reason = null; if (string.IsNullOrEmpty(ic.ResponseText)) { for (int i = 0; i < ic.RespCodes.Count; i++) { if (ic.RespCodes[i].IsError) { reason = ic.RespCodes[i].Message; break; } } } else { reason = ic.ResponseText; } if (!string.IsNullOrEmpty(reason)) { message = string.Format("The IMAP server replied to the '{0}' command with a '{1}' response: {2}", command, result, reason); } else { message = string.Format("The IMAP server replied to the '{0}' command with a '{1}' response.", command, result); } return(ic.Exception != null ? new ImapCommandException(ic.Response, message, ic.Exception) : new ImapCommandException(ic.Response, message)); }
/// <summary> /// Parses an untagged ID response. /// </summary> /// <param name="engine">The IMAP engine.</param> /// <param name="ic">The IMAP command.</param> /// <param name="index">The index.</param> public static void ParseImplementation(ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken(ic.CancellationToken); var implementation = new ImapImplementation(); ic.UserData = implementation; if (token.Type == ImapTokenType.Nil) { return; } if (token.Type != ImapTokenType.OpenParen) { throw ImapEngine.UnexpectedToken(token, false); } token = engine.PeekToken(ic.CancellationToken); while (token.Type != ImapTokenType.CloseParen) { var property = ImapUtils.ReadStringToken(engine, ic.CancellationToken); var value = ImapUtils.ReadNStringToken(engine, false, ic.CancellationToken); implementation.Properties[property] = value; token = engine.PeekToken(ic.CancellationToken); } // read the ')' token engine.ReadToken(ic.CancellationToken); }
/// <summary> /// Callback method to be used as the ImapCommand's ContinuationHandler. /// </summary> /// <remarks> /// Callback method to be used as the ImapCommand's ContinuationHandler. /// </remarks> /// <param name="engine">The ImapEngine.</param> /// <param name="ic">The ImapCommand.</param> /// <param name="text">The text.</param> /// <param name="doAsync"><c>true</c> if the command is being run asynchronously; otherwise, <c>false</c>.</param> /// <returns></returns> public Task ContinuationHandler(ImapEngine engine, ImapCommand ic, string text, bool doAsync) { Engine.State = ImapEngineState.Idle; registration = DoneToken.Register(IdleComplete); return(Task.FromResult(true)); }
static void ProcessUnmodified(ImapCommand ic, ref UniqueIdSet uids, ulong?modseq) { if (modseq.HasValue) { foreach (var rc in ic.RespCodes.OfType <ModifiedResponseCode> ()) { if (uids != null) { uids.AddRange(rc.UidSet); } else { uids = rc.UidSet; } } } }
static IList <int> GetUnmodified(ImapCommand ic, ulong?modseq) { if (modseq.HasValue) { var rc = ic.RespCodes.OfType <ModifiedResponseCode> ().FirstOrDefault(); if (rc != null) { var unmodified = new int[rc.UidSet.Count]; for (int i = 0; i < unmodified.Length; i++) { unmodified[i] = (int)(rc.UidSet[i].Id - 1); } return(unmodified); } } return(new int[0]); }
/// <summary> /// Create a new <see cref="ImapCommandException"/> based on the specified command name and <see cref="ImapCommand"/> state. /// </summary> /// <remarks> /// Create a new <see cref="ImapCommandException"/> based on the specified command name and <see cref="ImapCommand"/> state. /// </remarks> /// <returns>A new command exception.</returns> /// <param name="command">The command name.</param> /// <param name="ic">The command state.</param> internal static ImapCommandException Create (string command, ImapCommand ic) { var result = ic.Result.ToString ().ToUpperInvariant (); string message, reason = null; if (string.IsNullOrEmpty (ic.ResultText)) { for (int i = 0; i < ic.RespCodes.Count; i++) { if (ic.RespCodes[i].IsError) { reason = ic.RespCodes[i].Message; break; } } } else { reason = ic.ResultText; } if (!string.IsNullOrEmpty (reason)) message = string.Format ("The IMAP server replied to the '{0}' command with a '{1}' response: {2}", command, result, reason); else message = string.Format ("The IMAP server replied to the '{0}' command with a '{1}' response.", command, result); return ic.Exception != null ? new ImapCommandException (message, ic.Exception) : new ImapCommandException (message); }
/// <summary> /// Searches the subset of UIDs in the folder for messages matching the specified query. /// </summary> /// <remarks> /// The returned array of unique identifiers can be used with methods such as /// <see cref="IMailFolder.GetMessage(UniqueId,CancellationToken,ITransferProgress)"/>. /// </remarks> /// <returns>An array of matching UIDs.</returns> /// <param name="uids">The subset of UIDs</param> /// <param name="query">The search query.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="uids"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="query"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// One or more of the <paramref name="uids"/> is invalid. /// </exception> /// <exception cref="System.NotSupportedException"> /// One or more search terms in the <paramref name="query"/> are not supported by the IMAP server. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<UniqueId> Search (IList<UniqueId> uids, SearchQuery query, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatUidSet (uids); var args = new List<string> (); string charset; if (query == null) throw new ArgumentNullException ("query"); CheckState (true, false); if (uids.Count == 0) return new UniqueId[0]; var optimized = query.Optimize (new ImapSearchQueryOptimizer ()); var expr = BuildQueryExpression (optimized, args, out charset); var command = "UID SEARCH "; if ((Engine.Capabilities & ImapCapabilities.ESearch) != 0) command += "RETURN () "; if (args.Count > 0 && !Engine.UTF8Enabled) command += "CHARSET " + charset + " "; command += "UID " + set + " " + expr + "\r\n"; var ic = new ImapCommand (Engine, cancellationToken, this, command, args.ToArray ()); if ((Engine.Capabilities & ImapCapabilities.ESearch) != 0) ic.RegisterUntaggedHandler ("ESEARCH", ESearchMatches); // Note: always register the untagged SEARCH handler because some servers will brokenly // respond with "* SEARCH ..." instead of "* ESEARCH ..." even when using the extended // search syntax. ic.RegisterUntaggedHandler ("SEARCH", SearchMatches); Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SEARCH", ic); var results = (SearchResults) ic.UserData; if (results == null) return new UniqueId[0]; return results.UniqueIds; }
static void SearchMatches (ImapEngine engine, ImapCommand ic, int index) { var results = new SearchResults (); var uids = new List<UniqueId> (); ImapToken token; ulong modseq; uint uid; do { token = engine.PeekToken (ic.CancellationToken); // keep reading UIDs until we get to the end of the line or until we get a "(MODSEQ ####)" if (token.Type == ImapTokenType.Eoln || token.Type == ImapTokenType.OpenParen) break; token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out uid) || uid == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "SEARCH", token); uids.Add (new UniqueId (ic.Folder.UidValidity, uid)); } while (true); if (token.Type == ImapTokenType.OpenParen) { engine.ReadToken (ic.CancellationToken); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "SEARCH", token); var atom = (string) token.Value; switch (atom) { case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out modseq)) { Debug.WriteLine ("Expected 64-bit nz-number as the MODSEQ value, but got: {0}", token); throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } break; } token = engine.PeekToken (ic.CancellationToken); } while (token.Type != ImapTokenType.Eoln); } results.UniqueIds = uids; ic.UserData = results; }
/// <summary> /// Creates a new subfolder with the given name. /// </summary> /// <remarks> /// Creates a new subfolder with the given name. /// </remarks> /// <returns>The created folder.</returns> /// <param name="name">The name of the folder to create.</param> /// <param name="specialUses">A list of special uses for the folder being created.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="name"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// <paramref name="name"/> is empty. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="MailFolder.DirectorySeparator"/> is nil, and thus child folders cannot be created. /// </exception> /// <exception cref="System.NotSupportedException"> /// The IMAP server does not support the CREATE-SPECIAL-USE extension. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IMailFolder Create (string name, IEnumerable<SpecialFolder> specialUses, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException ("name"); if (!Engine.IsValidMailboxName (name, DirectorySeparator)) throw new ArgumentException ("The name is not a legal folder name.", "name"); CheckState (false, false); if (!string.IsNullOrEmpty (FullName) && DirectorySeparator == '\0') throw new InvalidOperationException ("Cannot create child folders."); if ((Engine.Capabilities & ImapCapabilities.CreateSpecialUse) == 0) throw new NotSupportedException ("The IMAP server does not support the CREATE-SPECIAL-USE extension."); var uses = new StringBuilder (); foreach (var use in specialUses) { if (uses.Length > 0) uses.Append (' '); switch (use) { case SpecialFolder.All: uses.Append ("\\All"); break; case SpecialFolder.Archive: uses.Append ("\\Archive"); break; case SpecialFolder.Drafts: uses.Append ("\\Drafts"); break; case SpecialFolder.Flagged: uses.Append ("\\Flagged"); break; case SpecialFolder.Junk: uses.Append ("\\Junk"); break; case SpecialFolder.Sent: uses.Append ("\\Sent"); break; case SpecialFolder.Trash: uses.Append ("\\Trash"); break; default: if (uses.Length > 0) uses.Length--; break; } } var fullName = !string.IsNullOrEmpty (FullName) ? FullName + DirectorySeparator + name : name; var command = string.Format ("CREATE %s (USE ({0}))\r\n", uses); var encodedName = Engine.EncodeMailboxName (fullName); var list = new List<ImapFolder> (); var createName = encodedName; ImapFolder folder; var ic = Engine.QueueCommand (cancellationToken, null, command, createName); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) { var useAttr = ic.RespCodes.FirstOrDefault (rc => rc.Type == ImapResponseCodeType.UseAttr); if (useAttr != null) throw new ImapCommandException (ic.Response, useAttr.Message); throw ImapCommandException.Create ("CREATE", ic); } ic = new ImapCommand (Engine, cancellationToken, null, "LIST \"\" %S\r\n", encodedName); ic.RegisterUntaggedHandler ("LIST", ImapUtils.ParseFolderList); ic.UserData = list; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("LIST", ic); if ((folder = list.FirstOrDefault ()) != null) folder.ParentFolder = this; Engine.AssignSpecialFolders (new [] { folder }); return folder; }
/// <summary> /// Gets a substream of the specified message. /// </summary> /// <remarks> /// 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. /// </remarks> /// <returns>The stream.</returns> /// <param name="uid">The UID of the message.</param> /// <param name="offset">The starting offset of the first desired byte.</param> /// <param name="count">The number of bytes desired.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentException"> /// <paramref name="uid"/> is invalid. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <para><paramref name="offset"/> is negative.</para> /// <para>-or-</para> /// <para><paramref name="count"/> is negative.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="MessageNotFoundException"> /// The IMAP server did not return the requested message stream. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override Stream GetStream (UniqueId uid, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (uid.Id == 0) throw new ArgumentException ("The uid is invalid.", "uid"); if (offset < 0) throw new ArgumentOutOfRangeException ("offset"); if (count < 0) throw new ArgumentOutOfRangeException ("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); Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchStream); ic.UserData = ctx; Engine.QueueCommand (ic); try { Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.Sections.TryGetValue (string.Empty, out stream)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); ctx.Sections.Remove (string.Empty); } finally { ctx.Dispose (); } return stream; }
/// <summary> /// Gets the specified message. /// </summary> /// <remarks> /// Gets the specified message. /// </remarks> /// <returns>The message.</returns> /// <param name="index">The index of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="index"/> is out of range. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="MessageNotFoundException"> /// The IMAP server did not return the requested message. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override MimeMessage GetMessage (int index, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException ("index"); CheckState (true, false); var ic = new ImapCommand (Engine, cancellationToken, this, "FETCH %d (BODY.PEEK[])\r\n", index + 1); var ctx = new FetchStreamContext (progress); Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchStream); ic.UserData = ctx; Engine.QueueCommand (ic); try { Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.Sections.TryGetValue (string.Empty, out stream)) throw new MessageNotFoundException ("The IMAP server did not return the requested message."); ctx.Sections.Remove (string.Empty); } finally { ctx.Dispose (); } return ParseMessage (stream, cancellationToken); }
void FetchStream (ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken (ic.CancellationToken); var labels = new MessageLabelsChangedEventArgs (index); var flags = new MessageFlagsChangedEventArgs (index); var ctx = (FetchStreamContext) ic.UserData; var section = new StringBuilder (); bool labelsChanged = false; bool flagsChanged = false; var buf = new byte[4096]; long nread = 0, size = 0; UniqueId? uid = null; Stream stream; int n; if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen || token.Type == ImapTokenType.Eoln) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var atom = (string) token.Value; int offset = 0, length; ulong modseq; uint value; switch (atom) { case "BODY": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); section.Clear (); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { section.Append (" ("); do { token = engine.ReadToken (ic.CancellationToken); 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: section.Append (engine.ReadLiteral (ic.CancellationToken)); section.Append (' '); break; case ImapTokenType.QString: case ImapTokenType.Atom: section.Append ((string) token.Value); break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } } while (true); if (section[section.Length - 1] == ' ') section.Length--; section.Append (')'); } else if (token.Type != ImapTokenType.Atom) { throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } else { section.Append ((string) token.Value); } } while (true); if (token.Type != ImapTokenType.CloseBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); 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, out offset); token = engine.ReadToken (ic.CancellationToken); } } switch (token.Type) { case ImapTokenType.Literal: length = (int) token.Value; size += length; stream = CreateStream (uid, section.ToString (), offset, length); try { while ((n = engine.Stream.Read (buf, 0, buf.Length, ic.CancellationToken)) > 0) { stream.Write (buf, 0, n); nread += n; ctx.Report (nread, size); } 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, section.ToString (), 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, section.ToString (), offset, 0); break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } if (uid.HasValue) ctx.Sections[section.ToString ()] = CommitStream (stream, uid.Value); else ctx.Sections[section.ToString ()] = stream; break; case "UID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out value) || value == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); uid = new UniqueId (UidValidity, value); foreach (var key in ctx.Sections.Keys.ToArray ()) ctx.Sections[key] = CommitStream (ctx.Sections[key], uid.Value); labels.UniqueId = uid.Value; flags.UniqueId = uid.Value; break; case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out modseq)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); labels.ModSeq = modseq; flags.ModSeq = modseq; 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 = ImapUtils.ParseFlagsList (engine, atom, flags.UserFlags, ic.CancellationToken); 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 = ImapUtils.ParseLabelsList (engine, ic.CancellationToken); labelsChanged = true; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); } } while (true); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); if (flagsChanged) ic.Folder.OnMessageFlagsChanged (flags); if (labelsChanged) ic.Folder.OnMessageLabelsChanged (labels); }
static void QResyncFetch (ImapEngine engine, ImapCommand ic, int index) { ic.Folder.OnFetch (engine, index, ic.CancellationToken); }
/// <summary> /// Sets the specified metadata. /// </summary> /// <remarks> /// Sets the specified metadata. /// </remarks> /// <returns>The metadata.</returns> /// <param name="metadata">The metadata.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="metadata"/> is <c>null</c>. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="System.NotSupportedException"> /// The IMAP server does not support the METADATA extension. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override void SetMetadata (MetadataCollection metadata, CancellationToken cancellationToken = default (CancellationToken)) { if (metadata == null) throw new ArgumentNullException ("metadata"); CheckState (false, false); if ((Engine.Capabilities & ImapCapabilities.Metadata) == 0) throw new NotSupportedException ("The IMAP server does not support the METADATA extension."); var command = new StringBuilder ("SETMETADATA %F ("); var args = new object[metadata.Count * 2 + 1]; int argc = 0; args[argc++] = this; for (int i = 0; i < metadata.Count; i++) { if (i > 0) command.Append (' '); command.Append ("%S %S"); args[argc++] = metadata[i].Tag.Id; args[argc++] = metadata[i].Value; } command.Append (")\r\n"); var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), args); Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SETMETADATA", ic); }
/// <summary> /// Parses an untagged LIST or LSUB response. /// </summary> /// <param name="engine">The IMAP engine.</param> /// <param name="ic">The IMAP command.</param> /// <param name="index">The index.</param> /// <param name="tok">The token.</param> public static void ParseFolderList(ImapEngine engine, ImapCommand ic, int index, ImapToken tok) { var token = engine.ReadToken(ic.CancellationToken); var list = (List <ImapFolder>)ic.UserData; var attrs = FolderAttributes.None; string encodedName; ImapFolder folder; char delim; // parse the folder attributes list if (token.Type != ImapTokenType.OpenParen) { throw ImapEngine.UnexpectedToken(token, false); } token = engine.ReadToken(ic.CancellationToken); while (token.Type == ImapTokenType.Flag || token.Type == ImapTokenType.Atom) { string atom = (string)token.Value; switch (atom) { case "\\NoInferiors": attrs |= FolderAttributes.NoInferiors; break; case "\\Noselect": attrs |= FolderAttributes.NoSelect; break; case "\\Marked": attrs |= FolderAttributes.Marked; break; case "\\Unmarked": attrs |= FolderAttributes.Unmarked; break; case "\\NonExistent": attrs |= FolderAttributes.NonExistent; break; case "\\Subscribed": attrs |= FolderAttributes.Subscribed; break; case "\\Remote": attrs |= FolderAttributes.Remote; break; case "\\HasChildren": attrs |= FolderAttributes.HasChildren; break; case "\\HasNoChildren": attrs |= FolderAttributes.HasNoChildren; break; case "\\All": attrs |= FolderAttributes.All; break; case "\\Archive": attrs |= FolderAttributes.Archive; break; case "\\Drafts": attrs |= FolderAttributes.Drafts; break; case "\\Flagged": attrs |= FolderAttributes.Flagged; break; case "\\Junk": attrs |= FolderAttributes.Junk; break; case "\\Sent": attrs |= FolderAttributes.Sent; break; case "\\Trash": attrs |= FolderAttributes.Trash; break; // XLIST flags: case "\\AllMail": attrs |= FolderAttributes.All; break; case "\\Important": attrs |= FolderAttributes.Flagged; break; case "\\Inbox": break; case "\\Spam": attrs |= FolderAttributes.Junk; break; case "\\Starred": attrs |= FolderAttributes.Flagged; break; } token = engine.ReadToken(ic.CancellationToken); } if (token.Type != ImapTokenType.CloseParen) { throw ImapEngine.UnexpectedToken(token, false); } // parse the path delimeter token = engine.ReadToken(ic.CancellationToken); if (token.Type == ImapTokenType.QString) { var qstring = (string)token.Value; delim = qstring[0]; } else if (token.Type == ImapTokenType.Nil) { delim = '\0'; } else { throw ImapEngine.UnexpectedToken(token, false); } // parse the folder name token = engine.ReadToken(ic.CancellationToken); switch (token.Type) { case ImapTokenType.Literal: encodedName = engine.ReadLiteral(ic.CancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: encodedName = (string)token.Value; break; default: throw ImapEngine.UnexpectedToken(token, false); } if (engine.FolderCache.TryGetValue(encodedName, out folder)) { folder.Attributes = (folder.Attributes & ~(FolderAttributes.Marked | FolderAttributes.Unmarked)) | attrs; } else { folder = new ImapFolder(engine, encodedName, attrs, delim); engine.FolderCache.Add(encodedName, folder); } list.Add(folder); }
/// <summary> /// Searches the subset of UIDs in the folder for messages matching the specified query, /// returning them in the preferred sort order. /// </summary> /// <remarks> /// Searches the folder for messages matching the specified query and ordering, /// returning only the requested search results. /// </remarks> /// <returns>The search results.</returns> /// <param name="options">The search options.</param> /// <param name="uids">The subset of UIDs</param> /// <param name="query">The search query.</param> /// <param name="orderBy">The sort order.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="uids"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="query"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="orderBy"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>One or more of the <paramref name="uids"/> is invalid.</para> /// <para>-or-</para> /// <para><paramref name="orderBy"/> is empty.</para> /// </exception> /// <exception cref="System.NotSupportedException"> /// <para>One or more search terms in the <paramref name="query"/> are not supported by the IMAP server.</para> /// <para>-or-</para> /// <para>The IMAP server does not support the ESORT extension.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override SearchResults Search (SearchOptions options, IList<UniqueId> uids, SearchQuery query, IList<OrderBy> orderBy, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatUidSet (uids); var args = new List<string> (); string charset; if (query == null) throw new ArgumentNullException ("query"); if (orderBy == null) throw new ArgumentNullException ("orderBy"); if (orderBy.Count == 0) throw new ArgumentException ("No sort order provided.", "orderBy"); CheckState (true, false); if ((Engine.Capabilities & ImapCapabilities.ESort) == 0) throw new NotSupportedException ("The IMAP server does not support the ESORT extension."); if ((Engine.Capabilities & ImapCapabilities.SortDisplay) == 0) { for (int i = 0; i < orderBy.Count; i++) { if (orderBy[i].Type == OrderByType.DisplayFrom || orderBy[i].Type == OrderByType.DisplayTo) throw new NotSupportedException ("The IMAP server does not support the SORT=DISPLAY extension."); } } if (uids.Count == 0) return new SearchResults (); var optimized = query.Optimize (new ImapSearchQueryOptimizer ()); var expr = BuildQueryExpression (optimized, args, out charset); var order = BuildSortOrder (orderBy); var command = "UID SORT RETURN ("; if ((options & SearchOptions.Count) != 0) command += "COUNT "; if ((options & SearchOptions.Min) != 0) command += "MIN "; if ((options & SearchOptions.Max) != 0) command += "MAX "; command = command.TrimEnd (); command += ") "; command += order + " " + charset + " UID " + set + " " + expr + "\r\n"; var ic = new ImapCommand (Engine, cancellationToken, this, command, args.ToArray ()); ic.RegisterUntaggedHandler ("ESEARCH", ESearchMatches); ic.UserData = new SearchResults (); Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SORT", ic); return (SearchResults) ic.UserData; }
/// <summary> /// Threads the messages in the folder that match the search query using the specified threading algorithm. /// </summary> /// <remarks> /// The <see cref="MessageThread.UniqueId"/> can be used with methods such as /// <see cref="IMailFolder.GetMessage(UniqueId,CancellationToken,ITransferProgress)"/>. /// </remarks> /// <returns>An array of message threads.</returns> /// <param name="uids">The subset of UIDs</param> /// <param name="algorithm">The threading algorithm to use.</param> /// <param name="query">The search query.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="algorithm"/> is not supported. /// </exception> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="uids"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="query"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para><paramref name="uids"/> is empty.</para> /// <para>-or-</para> /// <para>One or more of the <paramref name="uids"/> is invalid.</para> /// </exception> /// <exception cref="System.NotSupportedException"> /// <para>One or more search terms in the <paramref name="query"/> are not supported by the IMAP server.</para> /// <para>-or-</para> /// <para>The server does not support the THREAD extension.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<MessageThread> Thread (IList<UniqueId> uids, ThreadingAlgorithm algorithm, SearchQuery query, CancellationToken cancellationToken = default (CancellationToken)) { var method = algorithm.ToString ().ToUpperInvariant (); var set = ImapUtils.FormatUidSet (uids); var args = new List<string> (); string charset; if ((Engine.Capabilities & ImapCapabilities.Thread) == 0) throw new NotSupportedException ("The IMAP server does not support the THREAD extension."); if (!Engine.ThreadingAlgorithms.Contains (algorithm)) throw new ArgumentOutOfRangeException ("algorithm", "The specified threading algorithm is not supported."); if (query == null) throw new ArgumentNullException ("query"); CheckState (true, false); var optimized = query.Optimize (new ImapSearchQueryOptimizer ()); var expr = BuildQueryExpression (optimized, args, out charset); var command = "UID THREAD " + method + " " + charset + " "; command += "UID " + set + " " + expr + "\r\n"; var ic = new ImapCommand (Engine, cancellationToken, this, command, args.ToArray ()); ic.RegisterUntaggedHandler ("THREAD", ThreadMatches); Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("THREAD", ic); var threads = (IList<MessageThread>) ic.UserData; if (threads == null) return new MessageThread[0]; return threads; }
ImapCommand QueueAppend (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, CancellationToken cancellationToken, ITransferProgress progress) { string format = "APPEND %F"; if ((flags & SettableFlags) != 0) format += " " + ImapUtils.FormatFlagsList (flags, 0); if (date.HasValue) format += " \"" + ImapUtils.FormatInternalDate (date.Value) + "\""; format += " %L\r\n"; var ic = new ImapCommand (Engine, cancellationToken, null, options, format, this, message); ic.Progress = progress; Engine.QueueCommand (ic); return ic; }
/// <summary> /// Fetches the message summaries for the messages between the two indexes (inclusive) /// that have a higher mod-sequence value than the one specified. /// </summary> /// <remarks> /// <para>Fetches the message summaries for the messages between the two /// indexes (inclusive) that have a higher mod-sequence value than the one /// specified.</para> /// <para>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 <see cref="IMessageSummary"/> for /// messages that were requested as well as summaries for messages that were /// not requested at all.</para> /// </remarks> /// <returns>An enumeration of summaries for the requested messages.</returns> /// <param name="min">The minimum index.</param> /// <param name="max">The maximum index, or <c>-1</c> to specify no upper bound.</param> /// <param name="modseq">The mod-sequence value.</param> /// <param name="items">The message summary items to fetch.</param> /// <param name="fields">The desired header fields.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentOutOfRangeException"> /// <para><paramref name="min"/> is out of range.</para> /// <para>-or-</para> /// <para><paramref name="max"/> is out of range.</para> /// </exception> /// <exception cref="System.ArgumentNullException"> /// <paramref name="fields"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// <paramref name="fields"/> is empty. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.NotSupportedException"> /// The <see cref="ImapFolder"/> does not support mod-sequences. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<IMessageSummary> Fetch (int min, int max, ulong modseq, MessageSummaryItems items, HashSet<string> fields, CancellationToken cancellationToken = default (CancellationToken)) { if (min < 0 || min >= Count) throw new ArgumentOutOfRangeException ("min"); if (max != -1 && max < min) throw new ArgumentOutOfRangeException ("max"); if (fields == null) throw new ArgumentNullException ("fields"); if (fields.Count == 0) throw new ArgumentException ("The set of header fields cannot be empty.", "fields"); if (!SupportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); var query = FormatSummaryItems (ref items, fields); var command = string.Format ("FETCH {0} {1} (CHANGEDSINCE {2})\r\n", GetFetchRange (min, max), query, modseq); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (items); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItems); ic.UserData = ctx; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); return AsReadOnly (ctx.Results.Values); }
ImapCommand QueueMultiAppend (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, IList<DateTimeOffset> dates, CancellationToken cancellationToken, ITransferProgress progress) { var args = new List<object> (); string format = "APPEND %F"; args.Add (this); for (int i = 0; i < messages.Count; i++) { if ((flags[i] & SettableFlags) != 0) format += " " + ImapUtils.FormatFlagsList (flags[i], 0); if (dates != null) format += " \"" + ImapUtils.FormatInternalDate (dates[i]) + "\""; format += " %L"; args.Add (messages[i]); } format += "\r\n"; var ic = new ImapCommand (Engine, cancellationToken, null, options, format, args.ToArray ()); ic.Progress = progress; Engine.QueueCommand (ic); return ic; }
/// <summary> /// Creates a new subfolder with the given name. /// </summary> /// <remarks> /// Creates a new subfolder with the given name. /// </remarks> /// <returns>The created folder.</returns> /// <param name="name">The name of the folder to create.</param> /// <param name="isMessageFolder"><c>true</c> if the folder will be used to contain messages; otherwise <c>false</c>.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="name"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentException"> /// <paramref name="name"/> is empty. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The <see cref="MailFolder.DirectorySeparator"/> is nil, and thus child folders cannot be created. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IMailFolder Create (string name, bool isMessageFolder, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException ("name"); if (!Engine.IsValidMailboxName (name, DirectorySeparator)) throw new ArgumentException ("The name is not a legal folder name.", "name"); CheckState (false, false); if (!string.IsNullOrEmpty (FullName) && DirectorySeparator == '\0') throw new InvalidOperationException ("Cannot create child folders."); var fullName = !string.IsNullOrEmpty (FullName) ? FullName + DirectorySeparator + name : name; var encodedName = Engine.EncodeMailboxName (fullName); var list = new List<ImapFolder> (); var createName = encodedName; ImapFolder folder; if (!isMessageFolder) createName += DirectorySeparator; var ic = Engine.QueueCommand (cancellationToken, null, "CREATE %S\r\n", createName); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("CREATE", ic); ic = new ImapCommand (Engine, cancellationToken, null, "LIST \"\" %S\r\n", encodedName); ic.RegisterUntaggedHandler ("LIST", ImapUtils.ParseFolderList); ic.UserData = list; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("LIST", ic); if ((folder = list.FirstOrDefault ()) != null) folder.ParentFolder = this; return folder; }
/// <summary> /// Opens the folder using the requested folder access. /// </summary> /// <remarks> /// <para>This variant of the <see cref="Open(FolderAccess,System.Threading.CancellationToken)"/> /// method is meant for quick resynchronization of the folder. Before calling this method, /// the <see cref="ImapClient.EnableQuickResync(CancellationToken)"/> method MUST be called.</para> /// <para>You should also make sure to add listeners to the <see cref="MailFolder.MessagesVanished"/> and /// <see cref="MailFolder.MessageFlagsChanged"/> events to get notifications of changes since /// the last time the folder was opened.</para> /// </remarks> /// <returns>The <see cref="FolderAccess"/> state of the folder.</returns> /// <param name="access">The requested folder access.</param> /// <param name="uidValidity">The last known <see cref="MailFolder.UidValidity"/> value.</param> /// <param name="highestModSeq">The last known <see cref="MailFolder.HighestModSeq"/> value.</param> /// <param name="uids">The last known list of unique message identifiers.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="access"/> is not a valid value. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotFoundException"> /// The <see cref="ImapFolder"/> does not exist. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The QRESYNC feature has not been enabled. /// </exception> /// <exception cref="System.NotSupportedException"> /// The IMAP server does not support the QRESYNC extension. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override FolderAccess Open (FolderAccess access, uint uidValidity, ulong highestModSeq, IList<UniqueId> uids, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatUidSet (uids); if (access != FolderAccess.ReadOnly && access != FolderAccess.ReadWrite) throw new ArgumentOutOfRangeException ("access"); CheckState (false, false); if (IsOpen && Access == access) return access; if ((Engine.Capabilities & ImapCapabilities.QuickResync) == 0) throw new NotSupportedException ("The IMAP server does not support the QRESYNC extension."); if (!Engine.QResyncEnabled) throw new InvalidOperationException ("The QRESYNC extension has not been enabled."); var qresync = string.Format ("(QRESYNC ({0} {1}", uidValidity, highestModSeq); if (uids.Count > 0) qresync += " " + set; qresync += "))"; var command = string.Format ("{0} %F {1}\r\n", SelectOrExamine (access), qresync); var ic = new ImapCommand (Engine, cancellationToken, this, command, this); ic.RegisterUntaggedHandler ("FETCH", QResyncFetch); if (access == FolderAccess.ReadWrite) { // Note: if the server does not respond with a PERMANENTFLAGS response, // then we need to assume all flags are permanent. PermanentFlags = SettableFlags | MessageFlags.UserDefined; } else { PermanentFlags = MessageFlags.None; } try { Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create (access == FolderAccess.ReadOnly ? "EXAMINE" : "SELECT", ic); } catch { PermanentFlags = MessageFlags.None; throw; } if (Engine.Selected != null && Engine.Selected != this) { var folder = Engine.Selected; folder.PermanentFlags = MessageFlags.None; folder.AcceptedFlags = MessageFlags.None; folder.Access = FolderAccess.None; folder.OnClosed (); } Engine.State = ImapEngineState.Selected; Engine.Selected = this; OnOpened (); return Access; }
/// <summary> /// Gets the specified body part. /// </summary> /// <remarks> /// Gets the specified body part. /// </remarks> /// <returns>The body part.</returns> /// <param name="index">The index of the message.</param> /// <param name="partSpecifier">The body part specifier.</param> /// <param name="headersOnly"><c>true</c> if only the headers should be downloaded; otherwise, <c>false</c>></param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="partSpecifier"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="index"/> is out of range. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="MessageNotFoundException"> /// The IMAP server did not return the requested message. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public MimeEntity GetBodyPart (int index, string partSpecifier, bool headersOnly, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException ("index"); if (partSpecifier == null) throw new ArgumentNullException ("partSpecifier"); CheckState (true, false); string[] tags; var command = string.Format ("FETCH {0} ({1})\r\n", index + 1, GetBodyPartQuery (partSpecifier, headersOnly, out tags)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); ChainedStream chained; bool dispose = false; Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchStream); ic.UserData = ctx; Engine.QueueCommand (ic); try { Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); chained = new ChainedStream (); foreach (var tag in tags) { if (!ctx.Sections.TryGetValue (tag, out stream)) throw new MessageNotFoundException ("The IMAP server did not return the requested body part."); if (!(stream is MemoryStream || stream is MemoryBlockStream)) dispose = true; chained.Add (stream); } foreach (var tag in tags) ctx.Sections.Remove (tag); } finally { ctx.Dispose (); } var entity = ParseEntity (chained, dispose, cancellationToken); 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; }
void FetchSummaryItems (ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var ctx = (FetchSummaryContext) ic.UserData; IMessageSummary isummary; MessageSummary summary; if (!ctx.Results.TryGetValue (index, out isummary)) { summary = new MessageSummary (index); ctx.Results.Add (index, summary); } else { summary = (MessageSummary) isummary; } do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen || token.Type == ImapTokenType.Eoln) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var atom = (string) token.Value; string format; ulong value64; uint value; int idx; switch (atom) { case "INTERNALDATE": token = engine.ReadToken (ic.CancellationToken); switch (token.Type) { case ImapTokenType.QString: case ImapTokenType.Atom: summary.InternalDate = ImapUtils.ParseInternalDate ((string) token.Value); break; case ImapTokenType.Nil: summary.InternalDate = null; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } summary.Fields |= MessageSummaryItems.InternalDate; break; case "RFC822.SIZE": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out value)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.MessageSize; summary.Size = value; break; case "BODYSTRUCTURE": format = string.Format (ImapEngine.GenericItemSyntaxErrorFormat, "BODYSTRUCTURE", "{0}"); summary.Body = ImapUtils.ParseBody (engine, format, string.Empty, ic.CancellationToken); summary.Fields |= MessageSummaryItems.BodyStructure; break; case "BODY": token = engine.PeekToken (ic.CancellationToken); if (token.Type == ImapTokenType.OpenBracket) { // consume the '[' token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); // References and/or other headers were requested... do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { do { token = engine.ReadToken (ic.CancellationToken); 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: engine.ReadLiteral (ic.CancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } } while (true); } else if (token.Type != ImapTokenType.Atom) { throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } } while (true); if (token.Type != ImapTokenType.CloseBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Literal) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.References = new MessageIdList (); try { summary.Headers = engine.ParseHeaders (engine.Stream, ic.CancellationToken); } catch (FormatException) { // consume any remaining literal data... ReadLiteralData (engine, ic.CancellationToken); summary.Headers = new HeaderList (); } if ((idx = summary.Headers.IndexOf (HeaderId.References)) != -1) { var references = summary.Headers[idx]; var rawValue = references.RawValue; foreach (var msgid in MimeUtils.EnumerateReferences (rawValue, 0, rawValue.Length)) summary.References.Add (msgid); } summary.Fields |= MessageSummaryItems.References; } else { summary.Fields |= MessageSummaryItems.Body; try { format = string.Format (ImapEngine.GenericItemSyntaxErrorFormat, "BODY", "{0}"); summary.Body = ImapUtils.ParseBody (engine, format, string.Empty, ic.CancellationToken); } catch (ImapProtocolException ex) { if (!ex.UnexpectedToken) throw; // Note: GMail's IMAP implementation sometimes replies with completely broken BODY values // (see issue #32 for the `BODY ("ALTERNATIVE")` example), so to work around this nonsense, // we need to drop the remainder of this line. do { token = engine.PeekToken (ic.CancellationToken); if (token.Type == ImapTokenType.Eoln) break; token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.Literal) ReadLiteralData (engine, ic.CancellationToken); } while (true); return; } } break; case "ENVELOPE": summary.Envelope = ImapUtils.ParseEnvelope (engine, ic.CancellationToken); summary.Fields |= MessageSummaryItems.Envelope; break; case "FLAGS": summary.Flags = ImapUtils.ParseFlagsList (engine, atom, summary.UserFlags, ic.CancellationToken); summary.Fields |= MessageSummaryItems.Flags; break; case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out value64)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.ModSeq; summary.ModSeq = value64; break; case "UID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out value) || value == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.UniqueId = new UniqueId (ic.Folder.UidValidity, value); summary.Fields |= MessageSummaryItems.UniqueId; break; case "X-GM-MSGID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out value64) || value64 == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.GMailMessageId; summary.GMailMessageId = value64; break; case "X-GM-THRID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out value64) || value64 == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.GMailThreadId; summary.GMailThreadId = value64; break; case "X-GM-LABELS": summary.GMailLabels = ImapUtils.ParseLabelsList (engine, ic.CancellationToken); summary.Fields |= MessageSummaryItems.GMailLabels; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); } } while (true); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); if ((ctx.RequestedItems & summary.Fields) == ctx.RequestedItems) OnMessageSummaryFetched (summary); }
/// <summary> /// Gets a substream of the specified body part. /// </summary> /// <remarks> /// <para>Gets a substream of the specified message.</para> /// <para>For more information about how to construct the <paramref name="section"/>, /// see Section 6.4.5 of RFC3501.</para> /// </remarks> /// <returns>The stream.</returns> /// <param name="uid">The UID of the message.</param> /// <param name="section">The desired section of the message.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentException"> /// <paramref name="uid"/> is invalid. /// </exception> /// <exception cref="System.ArgumentNullException"> /// <paramref name="section"/> is <c>null</c>. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="MessageNotFoundException"> /// The IMAP server did not return the requested message stream. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override Stream GetStream (UniqueId uid, string section, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (uid.Id == 0) throw new ArgumentException ("The uid is invalid.", "uid"); if (section == null) throw new ArgumentNullException ("section"); CheckState (true, false); var command = string.Format ("UID FETCH {0} (BODY.PEEK[{1}])\r\n", uid.Id, section); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchStream); ic.UserData = ctx; Engine.QueueCommand (ic); try { Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.Sections.TryGetValue (section, out stream)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); ctx.Sections.Remove (section); } finally { ctx.Dispose (); } return stream; }
/// <summary> /// Fetches the message summaries for the specified message UIDs. /// </summary> /// <remarks> /// <para>Fetches the message summaries for the specified message UIDs.</para> /// <para>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 <see cref="IMessageSummary"/> for /// messages that were requested as well as summaries for messages that were /// not requested at all.</para> /// </remarks> /// <returns>An enumeration of summaries for the requested messages.</returns> /// <param name="uids">The UIDs.</param> /// <param name="items">The message summary items to fetch.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="uids"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="items"/> is empty. /// </exception> /// <exception cref="System.ArgumentException"> /// One or more of the <paramref name="uids"/> is invalid. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<IMessageSummary> Fetch (IList<UniqueId> uids, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatUidSet (uids); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException ("items"); CheckState (true, false); if (uids.Count == 0) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, null); var command = string.Format ("UID FETCH {0} {1}\r\n", set, query); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (items); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItems); ic.UserData = ctx; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); return AsReadOnly (ctx.Results.Values); }
/// <summary> /// Gets a substream of the specified message. /// </summary> /// <remarks> /// <para>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.</para> /// <para>For more information about how to construct the <paramref name="section"/>, /// see Section 6.4.5 of RFC3501.</para> /// </remarks> /// <returns>The stream.</returns> /// <param name="index">The index of the message.</param> /// <param name="section">The desired section of the message.</param> /// <param name="offset">The starting offset of the first desired byte.</param> /// <param name="count">The number of bytes desired.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress reporting mechanism.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="section"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <para><paramref name="index"/> is out of range.</para> /// <para>-or-</para> /// <para><paramref name="offset"/> is negative.</para> /// <para>-or-</para> /// <para><paramref name="count"/> is negative.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="MessageNotFoundException"> /// The IMAP server did not return the requested message stream. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override Stream GetStream (int index, string section, int offset, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException ("index"); if (section == null) throw new ArgumentNullException ("section"); if (offset < 0) throw new ArgumentOutOfRangeException ("offset"); if (count < 0) throw new ArgumentOutOfRangeException ("count"); CheckState (true, false); if (count == 0) return new MemoryStream (); var command = string.Format ("FETCH {0} (BODY.PEEK[{1}]<{2}.{3}>)\r\n", index + 1, section, offset, count); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchStreamContext (progress); Stream stream; ic.RegisterUntaggedHandler ("FETCH", FetchStream); ic.UserData = ctx; Engine.QueueCommand (ic); try { Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); if (!ctx.Sections.TryGetValue (section, out stream)) throw new MessageNotFoundException ("The IMAP server did not return the requested stream."); ctx.Sections.Remove (section); } finally { ctx.Dispose (); } return stream; }
/// <summary> /// Opens the folder using the requested folder access. /// </summary> /// <remarks> /// Opens the folder using the requested folder access. /// </remarks> /// <returns>The <see cref="FolderAccess"/> state of the folder.</returns> /// <param name="access">The requested folder access.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="access"/> is not a valid value. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotFoundException"> /// The <see cref="ImapFolder"/> does not exist. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override FolderAccess Open (FolderAccess access, CancellationToken cancellationToken = default (CancellationToken)) { if (access != FolderAccess.ReadOnly && access != FolderAccess.ReadWrite) throw new ArgumentOutOfRangeException ("access"); CheckState (false, false); if (IsOpen && Access == access) return access; var condstore = (Engine.Capabilities & ImapCapabilities.CondStore) != 0 ? " (CONDSTORE)" : string.Empty; var command = string.Format ("{0} %F{1}\r\n", SelectOrExamine (access), condstore); var ic = new ImapCommand (Engine, cancellationToken, this, command, this); if (access == FolderAccess.ReadWrite) { // Note: if the server does not respond with a PERMANENTFLAGS response, // then we need to assume all flags are permanent. PermanentFlags = SettableFlags | MessageFlags.UserDefined; } else { PermanentFlags = MessageFlags.None; } try { Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create (access == FolderAccess.ReadOnly ? "EXAMINE" : "SELECT", ic); } catch { PermanentFlags = MessageFlags.None; throw; } if (Engine.Selected != null && Engine.Selected != this) { var folder = Engine.Selected; folder.PermanentFlags = MessageFlags.None; folder.AcceptedFlags = MessageFlags.None; folder.Access = FolderAccess.None; folder.OnClosed (); } Engine.State = ImapEngineState.Selected; Engine.Selected = this; OnOpened (); return Access; }
static void ESearchMatches (ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken (ic.CancellationToken); var results = new SearchResults (); UniqueIdSet uids = null; //bool uid = false; uint min, max; ulong modseq; string atom; string tag; int count; if (token.Type == ImapTokenType.OpenParen) { // optional search correlator do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "ESEARCH", token); atom = (string) token.Value; if (atom == "TAG") { token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom && token.Type != ImapTokenType.QString) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "ESEARCH", token); tag = (string) token.Value; if (tag != ic.Tag) throw new ImapProtocolException ("Unexpected TAG value in untagged ESEARCH response: " + tag); } } while (true); token = engine.ReadToken (ic.CancellationToken); } if (token.Type == ImapTokenType.Atom && ((string) token.Value) == "UID") { token = engine.ReadToken (ic.CancellationToken); //uid = true; } do { if (token.Type == ImapTokenType.Eoln) { // unget the eoln token engine.Stream.UngetToken (token); break; } if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "ESEARCH", token); atom = (string) token.Value; token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "ESEARCH", token); switch (atom) { case "MODSEQ": if (!ulong.TryParse ((string) token.Value, out modseq)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); results.ModSeq = modseq; break; case "COUNT": if (!int.TryParse ((string) token.Value, out count)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); results.Count = count; break; case "MIN": if (!uint.TryParse ((string) token.Value, out min)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); results.Min = new UniqueId (ic.Folder.UidValidity, min); break; case "MAX": if (!uint.TryParse ((string) token.Value, out max)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); results.Max = new UniqueId (ic.Folder.UidValidity, max); break; case "ALL": if (!UniqueIdSet.TryParse ((string) token.Value, ic.Folder.UidValidity, out uids)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); results.Count = uids.Count; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "ESEARCH", token); } token = engine.ReadToken (ic.CancellationToken); } while (true); results.UniqueIds = uids ?? new UniqueIdSet (); ic.UserData = results; }
/// <summary> /// Fetches the message summaries for the specified message UIDs that have a /// higher mod-sequence value than the one specified. /// </summary> /// <remarks> /// <para>Fetches the message summaries for the specified message UIDs that /// have a higher mod-sequence value than the one specified.</para> /// <para>If the IMAP server supports the QRESYNC extension and the application has /// enabled this feature via <see cref="ImapClient.EnableQuickResync(CancellationToken)"/>, /// then this method will emit <see cref="MailFolder.MessagesVanished"/> events for messages /// that have vanished since the specified mod-sequence value.</para> /// <para>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 <see cref="IMessageSummary"/> for /// messages that were requested as well as summaries for messages that were /// not requested at all.</para> /// </remarks> /// <returns>An enumeration of summaries for the requested messages.</returns> /// <param name="uids">The UIDs.</param> /// <param name="modseq">The mod-sequence value.</param> /// <param name="items">The message summary items to fetch.</param> /// <param name="fields">The desired header fields.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="uids"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="fields"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>One or more of the <paramref name="uids"/> is invalid.</para> /// <para>-or-</para> /// <para><paramref name="fields"/> is empty.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.NotSupportedException"> /// The <see cref="ImapFolder"/> does not support mod-sequences. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<IMessageSummary> Fetch (IList<UniqueId> uids, ulong modseq, MessageSummaryItems items, HashSet<string> fields, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatUidSet (uids); if (fields == null) throw new ArgumentNullException ("fields"); if (fields.Count == 0) throw new ArgumentException ("The set of header fields cannot be empty.", "fields"); 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, fields); var vanished = Engine.QResyncEnabled ? " VANISHED" : string.Empty; var command = string.Format ("UID FETCH {0} {1} (CHANGEDSINCE {2}{3})\r\n", set, query, modseq, vanished); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (items); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItems); ic.UserData = ctx; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); return AsReadOnly (ctx.Results.Values); }
/// <summary> /// Searches the subset of UIDs in the folder for messages matching the specified query. /// </summary> /// <remarks> /// Searches the fsubset of UIDs in the folder for messages matching the specified query, /// returning only the specified search results. /// </remarks> /// <returns>The search results.</returns> /// <param name="options">The search options.</param> /// <param name="uids">The subset of UIDs</param> /// <param name="query">The search query.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="uids"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="query"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// One or more of the <paramref name="uids"/> is invalid. /// </exception> /// <exception cref="System.NotSupportedException"> /// <para>One or more search terms in the <paramref name="query"/> are not supported by the IMAP server.</para> /// <para>-or-</para> /// <para>The IMAP server does not support the ESEARCH extension.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override SearchResults Search (SearchOptions options, IList<UniqueId> uids, SearchQuery query, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatUidSet (uids); var args = new List<string> (); string charset; if (query == null) throw new ArgumentNullException ("query"); CheckState (true, false); if ((Engine.Capabilities & ImapCapabilities.ESearch) == 0) throw new NotSupportedException ("The IMAP server does not support the ESEARCH extension."); if (uids.Count == 0) return new SearchResults (); var optimized = query.Optimize (new ImapSearchQueryOptimizer ()); var expr = BuildQueryExpression (optimized, args, out charset); var command = "UID SEARCH RETURN ("; if ((options & SearchOptions.Count) != 0) command += "COUNT "; if ((options & SearchOptions.Min) != 0) command += "MIN "; if ((options & SearchOptions.Max) != 0) command += "MAX "; command = command.TrimEnd (); command += ") "; if (args.Count > 0 && !Engine.UTF8Enabled) command += "CHARSET " + charset + " "; command += "UID " + set + " " + expr + "\r\n"; var ic = new ImapCommand (Engine, cancellationToken, this, command, args.ToArray ()); ic.RegisterUntaggedHandler ("ESEARCH", ESearchMatches); ic.UserData = new SearchResults (); Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SEARCH", ic); return (SearchResults) ic.UserData; }
/// <summary> /// Fetches the message summaries for the specified message indexes. /// </summary> /// <remarks> /// <para>Fetches the message summaries for the specified message indexes.</para> /// <para>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 <see cref="IMessageSummary"/> for /// messages that were requested as well as summaries for messages that were /// not requested at all.</para> /// </remarks> /// <returns>An enumeration of summaries for the requested messages.</returns> /// <param name="indexes">The indexes.</param> /// <param name="items">The message summary items to fetch.</param> /// <param name="fields">The desired header fields.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="indexes"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="fields"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para>One or more of the <paramref name="indexes"/> is invalid.</para> /// <para>-or-</para> /// <para><paramref name="fields"/> is empty.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<IMessageSummary> Fetch (IList<int> indexes, MessageSummaryItems items, HashSet<string> fields, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatIndexSet (indexes); if (fields == null) throw new ArgumentNullException ("fields"); if (fields.Count == 0) throw new ArgumentException ("The set of header fields cannot be empty.", "fields"); CheckState (true, false); if (indexes.Count == 0) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, fields); var command = string.Format ("FETCH {0} {1}\r\n", set, query); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (items); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItems); ic.UserData = ctx; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); return AsReadOnly (ctx.Results.Values); }
static void ThreadMatches (ImapEngine engine, ImapCommand ic, int index) { ic.UserData = ImapUtils.ParseThreads (engine, ic.Folder.UidValidity, ic.CancellationToken); }
/// <summary> /// Fetches the message summaries for the specified message indexes that have a /// higher mod-sequence value than the one specified. /// </summary> /// <remarks> /// <para>Fetches the message summaries for the specified message indexes that /// have a higher mod-sequence value than the one specified.</para> /// <para>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 <see cref="IMessageSummary"/> for /// messages that were requested as well as summaries for messages that were /// not requested at all.</para> /// </remarks> /// <returns>An enumeration of summaries for the requested messages.</returns> /// <param name="indexes">The indexes.</param> /// <param name="modseq">The mod-sequence value.</param> /// <param name="items">The message summary items to fetch.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="indexes"/> is <c>null</c>. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// <paramref name="items"/> is empty. /// </exception> /// <exception cref="System.ArgumentException"> /// One or more of the <paramref name="indexes"/> is invalid. /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.NotSupportedException"> /// The <see cref="ImapFolder"/> does not support mod-sequences. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<IMessageSummary> Fetch (IList<int> indexes, ulong modseq, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { var set = ImapUtils.FormatIndexSet (indexes); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException ("items"); if (!SupportsModSeq) throw new NotSupportedException ("The ImapFolder does not support mod-sequences."); CheckState (true, false); if (indexes.Count == 0) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, null); var command = string.Format ("FETCH {0} {1} (CHANGEDSINCE {2})\r\n", set, query, modseq); var ic = new ImapCommand (Engine, cancellationToken, this, command); var ctx = new FetchSummaryContext (items); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItems); ic.UserData = ctx; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); return AsReadOnly (ctx.Results.Values); }
/// <summary> /// Gets the subfolders. /// </summary> /// <remarks> /// Gets the subfolders. /// </remarks> /// <returns>The subfolders.</returns> /// <param name="items">The status items to pre-populate.</param> /// <param name="subscribedOnly">If set to <c>true</c>, only subscribed folders will be listed.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IEnumerable<IMailFolder> GetSubfolders (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken)) { CheckState (false, false); var pattern = EncodedName.Length > 0 ? EncodedName + DirectorySeparator : string.Empty; var children = new List<IMailFolder> (); var status = items != StatusItems.None; var list = new List<ImapFolder> (); var command = new StringBuilder (); var lsub = subscribedOnly; if (subscribedOnly) { if ((Engine.Capabilities & ImapCapabilities.ListExtended) != 0) { command.Append ("LIST (SUBSCRIBED)"); lsub = false; } else { command.Append ("LSUB"); } } else { command.Append ("LIST"); } command.Append (" \"\" %S"); if (!lsub) { if (items != StatusItems.None && (Engine.Capabilities & ImapCapabilities.ListStatus) != 0) { command.Append (" RETURN ("); if ((Engine.Capabilities & ImapCapabilities.ListExtended) != 0) { if (!subscribedOnly) command.Append ("SUBSCRIBED "); command.Append ("CHILDREN "); } command.AppendFormat ("STATUS ({0})", Engine.GetStatusQuery (items)); command.Append (')'); status = false; } else if ((Engine.Capabilities & ImapCapabilities.ListExtended) != 0) { command.Append (" RETURN ("); if (!subscribedOnly) command.Append ("SUBSCRIBED "); command.Append ("CHILDREN"); command.Append (')'); } } command.Append ("\r\n"); var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), pattern + "%"); ic.RegisterUntaggedHandler (lsub ? "LSUB" : "LIST", ImapUtils.ParseFolderList); ic.UserData = list; Engine.QueueCommand (ic); Engine.Wait (ic); // Note: Some broken IMAP servers (*cough* SmarterMail 13.0 *cough*) return folders // that are not children of the folder we requested, so we need to filter those // folders out of the list that we'll be returning to our caller. // // See https://github.com/jstedfast/MailKit/issues/149 for more details. var prefix = FullName.Length > 0 ? FullName + DirectorySeparator : string.Empty; prefix = ImapUtils.CanonicalizeMailboxName (prefix, DirectorySeparator); foreach (var folder in list) { var canonicalFullName = ImapUtils.CanonicalizeMailboxName (folder.FullName, folder.DirectorySeparator); var canonicalName = ImapUtils.IsInbox (folder.FullName) ? "INBOX" : folder.Name; if (canonicalFullName != prefix + canonicalName) continue; if (lsub) { // the LSUB command does not send \Subscribed flags so we need to add them ourselves folder.Attributes |= FolderAttributes.Subscribed; } folder.ParentFolder = this; children.Add (folder); } ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create (lsub ? "LSUB" : "LIST", ic); if (status) { for (int i = 0; i < children.Count; i++) children[i].Status (items, cancellationToken); } return children; }
/// <summary> /// Fetches the message summaries for the messages between the two indexes, inclusive. /// </summary> /// <remarks> /// <para>Fetches the message summaries for the messages between the two /// indexes, inclusive.</para> /// <para>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 <see cref="IMessageSummary"/> for /// messages that were requested as well as summaries for messages that were /// not requested at all.</para> /// </remarks> /// <returns>An enumeration of summaries for the requested messages.</returns> /// <param name="min">The minimum index.</param> /// <param name="max">The maximum index, or <c>-1</c> to specify no upper bound.</param> /// <param name="items">The message summary items to fetch.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.ArgumentOutOfRangeException"> /// <para><paramref name="min"/> is out of range.</para> /// <para>-or-</para> /// <para><paramref name="max"/> is out of range.</para> /// <para>-or-</para> /// <para><paramref name="items"/> is empty.</para> /// </exception> /// <exception cref="System.ObjectDisposedException"> /// The <see cref="ImapClient"/> has been disposed. /// </exception> /// <exception cref="ServiceNotConnectedException"> /// The <see cref="ImapClient"/> is not connected. /// </exception> /// <exception cref="ServiceNotAuthenticatedException"> /// The <see cref="ImapClient"/> is not authenticated. /// </exception> /// <exception cref="FolderNotOpenException"> /// The <see cref="ImapFolder"/> is not currently open. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </exception> /// <exception cref="System.IO.IOException"> /// An I/O error occurred. /// </exception> /// <exception cref="ImapProtocolException"> /// The server's response contained unexpected tokens. /// </exception> /// <exception cref="ImapCommandException"> /// The server replied with a NO or BAD response. /// </exception> public override IList<IMessageSummary> Fetch (int min, int max, MessageSummaryItems items, CancellationToken cancellationToken = default (CancellationToken)) { if (min < 0 || min > Count) throw new ArgumentOutOfRangeException ("min"); if (max != -1 && max < min) throw new ArgumentOutOfRangeException ("max"); if (items == MessageSummaryItems.None) throw new ArgumentOutOfRangeException ("items"); CheckState (true, false); if (min == Count) return new IMessageSummary[0]; var query = FormatSummaryItems (ref items, null); 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 (items); ic.RegisterUntaggedHandler ("FETCH", FetchSummaryItems); ic.UserData = ctx; Engine.QueueCommand (ic); Engine.Wait (ic); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("FETCH", ic); return AsReadOnly (ctx.Results.Values); }