// // ImapFolder.cs // // Author: Jeffrey Stedfast // // Copyright (c) 2013-2020 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.Text; using System.Threading; using System.Globalization; using System.Threading.Tasks; using System.Collections.Generic; using MimeKit; using MailKit.Search; namespace MailKit.Net.Imap { /// /// An IMAP folder. /// /// /// An IMAP folder. /// /// /// /// /// /// /// public partial class ImapFolder : MailFolder, IImapFolder { bool supportsModSeq; /// /// Initializes a new instance of the class. /// /// /// Creates a new . /// If you subclass , you will also need to subclass /// and override the /// /// method in order to return a new instance of your ImapFolder subclass. /// /// The constructor arguments. /// /// is null. /// public ImapFolder (ImapFolderConstructorArgs args) { if (args == null) throw new ArgumentNullException (nameof (args)); InitializeProperties (args); } void InitializeProperties (ImapFolderConstructorArgs args) { DirectorySeparator = args.DirectorySeparator; EncodedName = args.EncodedName; Attributes = args.Attributes; FullName = args.FullName; Engine = args.Engine; Name = args.Name; } /// /// Get the IMAP command engine. /// /// /// Gets the IMAP command engine. /// /// The engine. internal ImapEngine Engine { get; private set; } /// /// Get the encoded name of the folder. /// /// /// Gets the encoded name of the folder. /// /// The encoded name. internal string EncodedName { get; set; } /// /// Gets an object that can be used to synchronize access to the IMAP server. /// /// /// Gets an object that can be used to synchronize access to the IMAP server. /// When using the non-Async methods from multiple threads, it is important to lock the /// object for thread safety when using the synchronous methods. /// /// The lock object. public override object SyncRoot { get { return Engine; } } /// /// Get the threading algorithms supported by the folder. /// /// /// Get the threading algorithms supported by the folder. /// /// The supported threading algorithms. public override HashSet ThreadingAlgorithms { get { return Engine.ThreadingAlgorithms; } } /// /// Determine whether or not an supports a feature. /// /// /// Determines whether or not an supports a feature. /// /// The desired feature. /// true if the feature is supported; otherwise, false. public override bool Supports (FolderFeature feature) { switch (feature) { case FolderFeature.AccessRights: return (Engine.Capabilities & ImapCapabilities.Acl) != 0; case FolderFeature.Annotations: return AnnotationAccess != AnnotationAccess.None; case FolderFeature.Metadata: return (Engine.Capabilities & ImapCapabilities.Metadata) != 0; case FolderFeature.ModSequences: return supportsModSeq; case FolderFeature.QuickResync: return Engine.QResyncEnabled; case FolderFeature.Quotas: return (Engine.Capabilities & ImapCapabilities.Quota) != 0; case FolderFeature.Sorting: return (Engine.Capabilities & ImapCapabilities.Sort) != 0; case FolderFeature.Threading: return (Engine.Capabilities & ImapCapabilities.Thread) != 0; case FolderFeature.UTF8: return Engine.UTF8Enabled; default: return false; } } void CheckState (bool open, bool rw) { if (Engine.IsDisposed) throw new ObjectDisposedException (nameof (ImapClient)); if (!Engine.IsConnected) throw new ServiceNotConnectedException ("The ImapClient is not connected."); if (Engine.State < ImapEngineState.Authenticated) throw new ServiceNotAuthenticatedException ("The ImapClient is not authenticated."); if (open) { var access = rw ? FolderAccess.ReadWrite : FolderAccess.ReadOnly; if (!IsOpen || Access < access) throw new FolderNotOpenException (FullName, access); } } void CheckAllowIndexes () { // Indexes ("Message Sequence Numbers" or MSNs in the RFCs) and * are not stable while MessageNew/MessageExpunge is registered for SELECTED and therefore should not be used // https://tools.ietf.org/html/rfc5465#section-5.2 if (Engine.NotifySelectedNewExpunge) throw new InvalidOperationException ("Indexes and '*' cannot be used while MessageNew/MessageExpunge is registered with NOTIFY for SELECTED."); } internal void Reset () { // basic state PermanentFlags = MessageFlags.None; AcceptedFlags = MessageFlags.None; Access = FolderAccess.None; // annotate state AnnotationAccess = AnnotationAccess.None; AnnotationScopes = AnnotationScope.None; MaxAnnotationSize = 0; // condstore state supportsModSeq = false; HighestModSeq = 0; } /// /// Notifies the folder that a parent folder has been renamed. /// /// /// Updates the property. /// protected override void OnParentFolderRenamed () { var oldEncodedName = EncodedName; FullName = ParentFolder.FullName + DirectorySeparator + Name; EncodedName = Engine.EncodeMailboxName (FullName); Engine.FolderCache.Remove (oldEncodedName); Engine.FolderCache[EncodedName] = this; Reset (); if (Engine.Selected == this) { Engine.State = ImapEngineState.Authenticated; Engine.Selected = null; OnClosed (); } } void ProcessResponseCodes (ImapCommand ic, IMailFolder folder, bool throwNotFound = true) { bool tryCreate = false; foreach (var code in ic.RespCodes) { switch (code.Type) { case ImapResponseCodeType.PermanentFlags: PermanentFlags = ((PermanentFlagsResponseCode) code).Flags; break; case ImapResponseCodeType.ReadOnly: if (code.IsTagged) Access = FolderAccess.ReadOnly; break; case ImapResponseCodeType.ReadWrite: if (code.IsTagged) Access = FolderAccess.ReadWrite; break; case ImapResponseCodeType.TryCreate: tryCreate = true; break; case ImapResponseCodeType.UidNext: UidNext = ((UidNextResponseCode) code).Uid; break; case ImapResponseCodeType.UidValidity: var uidValidity = ((UidValidityResponseCode) code).UidValidity; if (IsOpen) UpdateUidValidity (uidValidity); else UidValidity = uidValidity; break; case ImapResponseCodeType.Unseen: FirstUnread = ((UnseenResponseCode) code).Index; break; case ImapResponseCodeType.HighestModSeq: var highestModSeq = ((HighestModSeqResponseCode) code).HighestModSeq; supportsModSeq = true; if (IsOpen) UpdateHighestModSeq (highestModSeq); else HighestModSeq = highestModSeq; break; case ImapResponseCodeType.NoModSeq: supportsModSeq = false; HighestModSeq = 0; break; case ImapResponseCodeType.MailboxId: // Note: an untagged MAILBOX resp-code is returned on SELECT/EXAMINE while // a *tagged* MAILBOXID resp-code is returned on CREATE. if (!code.IsTagged) Id = ((MailboxIdResponseCode) code).MailboxId; break; case ImapResponseCodeType.Annotations: var annotations = (AnnotationsResponseCode) code; AnnotationAccess = annotations.Access; AnnotationScopes = annotations.Scopes; MaxAnnotationSize = annotations.MaxSize; break; } } if (tryCreate && throwNotFound && folder != null) throw new FolderNotFoundException (folder.FullName); } static ImapResponseCode GetResponseCode (ImapCommand ic, ImapResponseCodeType type) { for (int i = 0; i < ic.RespCodes.Count; i++) { if (ic.RespCodes[i].Type == type) return ic.RespCodes[i]; } return null; } #region IMailFolder implementation /// /// Gets a value indicating whether the folder is currently open. /// /// /// Gets a value indicating whether the folder is currently open. /// /// true if the folder is currently open; otherwise, false. public override bool IsOpen { get { return Engine.Selected == this; } } static string SelectOrExamine (FolderAccess access) { return access == FolderAccess.ReadOnly ? "EXAMINE" : "SELECT"; } static Task QResyncFetchAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { return ic.Folder.OnFetchAsync (engine, index, doAsync, ic.CancellationToken); } async Task OpenAsync (ImapCommand ic, FolderAccess access, bool doAsync, CancellationToken cancellationToken) { Reset (); 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); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); 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.Reset (); folder.OnClosed (); } Engine.State = ImapEngineState.Selected; Engine.Selected = this; OnOpened (); return Access; } Task OpenAsync (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, bool doAsync, CancellationToken cancellationToken) { if (access != FolderAccess.ReadOnly && access != FolderAccess.ReadWrite) throw new ArgumentOutOfRangeException (nameof (access)); if (uids == null) throw new ArgumentNullException (nameof (uids)); CheckState (false, false); if ((Engine.Capabilities & ImapCapabilities.QuickResync) == 0) throw new NotSupportedException ("The IMAP server does not support the QRESYNC extension."); if (!Supports (FolderFeature.QuickResync)) throw new InvalidOperationException ("The QRESYNC extension has not been enabled."); string qresync; if ((Engine.Capabilities & ImapCapabilities.Annotate) != 0 && Engine.QuirksMode != ImapQuirksMode.SunMicrosystems) qresync = string.Format (CultureInfo.InvariantCulture, "(ANNOTATE QRESYNC ({0} {1}", uidValidity, highestModSeq); else qresync = string.Format (CultureInfo.InvariantCulture, "(QRESYNC ({0} {1}", uidValidity, highestModSeq); if (uids.Count > 0) { var set = UniqueIdSet.ToString (uids); 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", QResyncFetchAsync); return OpenAsync (ic, access, doAsync, cancellationToken); } /// /// Open the folder using the requested folder access. /// /// /// This variant of the /// method is meant for quick resynchronization of the folder. Before calling this method, /// the method MUST be called. /// You should also make sure to add listeners to the and /// events to get notifications of changes since /// the last time the folder was opened. /// /// The state of the folder. /// The requested folder access. /// The last known value. /// The last known value. /// The last known list of unique message identifiers. /// The cancellation token. /// /// is not a valid value. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The QRESYNC feature has not been enabled. /// /// /// The IMAP server does not support the QRESYNC extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override FolderAccess Open (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, CancellationToken cancellationToken = default (CancellationToken)) { return OpenAsync (access, uidValidity, highestModSeq, uids, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously open the folder using the requested folder access. /// /// /// This variant of the /// method is meant for quick resynchronization of the folder. Before calling this method, /// the method MUST be called. /// You should also make sure to add listeners to the and /// events to get notifications of changes since /// the last time the folder was opened. /// /// The state of the folder. /// The requested folder access. /// The last known value. /// The last known value. /// The last known list of unique message identifiers. /// The cancellation token. /// /// is not a valid value. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The QRESYNC feature has not been enabled. /// /// /// The IMAP server does not support the QRESYNC extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task OpenAsync (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, CancellationToken cancellationToken = default (CancellationToken)) { return OpenAsync (access, uidValidity, highestModSeq, uids, true, cancellationToken); } Task OpenAsync (FolderAccess access, bool doAsync, CancellationToken cancellationToken) { if (access != FolderAccess.ReadOnly && access != FolderAccess.ReadWrite) throw new ArgumentOutOfRangeException (nameof (access)); CheckState (false, false); var @params = string.Empty; if ((Engine.Capabilities & ImapCapabilities.CondStore) != 0) @params += "CONDSTORE"; if ((Engine.Capabilities & ImapCapabilities.Annotate) != 0 && Engine.QuirksMode != ImapQuirksMode.SunMicrosystems) @params += " ANNOTATE"; if (@params.Length > 0) @params = " (" + @params.TrimStart () + ")"; var command = string.Format ("{0} %F{1}\r\n", SelectOrExamine (access), @params); var ic = new ImapCommand (Engine, cancellationToken, this, command, this); return OpenAsync (ic, access, doAsync, cancellationToken); } /// /// Open the folder using the requested folder access. /// /// /// Opens the folder using the requested folder access. /// /// The state of the folder. /// The requested folder access. /// The cancellation token. /// /// is not a valid value. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override FolderAccess Open (FolderAccess access, CancellationToken cancellationToken = default (CancellationToken)) { return OpenAsync (access, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously open the folder using the requested folder access. /// /// /// Opens the folder using the requested folder access. /// /// The state of the folder. /// The requested folder access. /// The cancellation token. /// /// is not a valid value. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task OpenAsync (FolderAccess access, CancellationToken cancellationToken = default (CancellationToken)) { return OpenAsync (access, true, cancellationToken); } async Task CloseAsync (bool expunge, bool doAsync, CancellationToken cancellationToken) { CheckState (true, expunge); ImapCommand ic; if (expunge) { ic = Engine.QueueCommand (cancellationToken, this, "CLOSE\r\n"); } else if ((Engine.Capabilities & ImapCapabilities.Unselect) != 0) { ic = Engine.QueueCommand (cancellationToken, this, "UNSELECT\r\n"); } else { ic = null; } if (ic != null) { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create (expunge ? "CLOSE" : "UNSELECT", ic); } Reset (); if (Engine.Selected == this) { Engine.State = ImapEngineState.Authenticated; Engine.Selected = null; OnClosed (); } } /// /// Close the folder, optionally expunging the messages marked for deletion. /// /// /// Closes the folder, optionally expunging the messages marked for deletion. /// /// If set to true, expunge. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Close (bool expunge = false, CancellationToken cancellationToken = default (CancellationToken)) { CloseAsync (expunge, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously close the folder, optionally expunging the messages marked for deletion. /// /// /// Closes the folder, optionally expunging the messages marked for deletion. /// /// An asynchronous task context. /// If set to true, expunge. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task CloseAsync (bool expunge = false, CancellationToken cancellationToken = default (CancellationToken)) { return CloseAsync (expunge, true, cancellationToken); } async Task GetCreatedFolderAsync (string encodedName, string id, bool specialUse, bool doAsync, CancellationToken cancellationToken) { var ic = new ImapCommand (Engine, cancellationToken, null, "LIST \"\" %S\r\n", encodedName); var list = new List (); ImapFolder folder; ic.RegisterUntaggedHandler ("LIST", ImapUtils.ParseFolderListAsync); ic.UserData = list; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("LIST", ic); if ((folder = ImapEngine.GetFolder (list, encodedName)) != null) { folder.ParentFolder = this; folder.Id = id; if (specialUse) Engine.AssignSpecialFolder (folder); } return folder; } async Task CreateAsync (string name, bool isMessageFolder, bool doAsync, CancellationToken cancellationToken) { if (name == null) throw new ArgumentNullException (nameof (name)); if (!Engine.IsValidMailboxName (name, DirectorySeparator)) throw new ArgumentException ("The name is not a legal folder name.", nameof (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 createName = encodedName; if (!isMessageFolder && Engine.QuirksMode != ImapQuirksMode.GMail) createName += DirectorySeparator; var ic = Engine.QueueCommand (cancellationToken, null, "CREATE %S\r\n", createName); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok && GetResponseCode (ic, ImapResponseCodeType.AlreadyExists) == null) throw ImapCommandException.Create ("CREATE", ic); var code = (MailboxIdResponseCode) GetResponseCode (ic, ImapResponseCodeType.MailboxId); var id = code?.MailboxId; var created = await GetCreatedFolderAsync (encodedName, id, false, doAsync, cancellationToken).ConfigureAwait (false); Engine.OnFolderCreated (created); return created; } /// /// Create a new subfolder with the given name. /// /// /// Creates a new subfolder with the given name. /// /// The created folder. /// The name of the folder to create. /// true if the folder will be used to contain messages; otherwise false. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is nil, and thus child folders cannot be created. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IMailFolder Create (string name, bool isMessageFolder, CancellationToken cancellationToken = default (CancellationToken)) { return CreateAsync (name, isMessageFolder, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously create a new subfolder with the given name. /// /// /// Creates a new subfolder with the given name. /// /// The created folder. /// The name of the folder to create. /// true if the folder will be used to contain messages; otherwise false. /// The cancellation token. /// /// is null. /// /// /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is nil, and thus child folders cannot be created. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task CreateAsync (string name, bool isMessageFolder, CancellationToken cancellationToken = default (CancellationToken)) { return CreateAsync (name, isMessageFolder, true, cancellationToken); } async Task CreateAsync (string name, IEnumerable specialUses, bool doAsync, CancellationToken cancellationToken) { if (name == null) throw new ArgumentNullException (nameof (name)); if (!Engine.IsValidMailboxName (name, DirectorySeparator)) throw new ArgumentException ("The name is not a legal folder name.", nameof (name)); if (specialUses == null) throw new ArgumentNullException (nameof (specialUses)); 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 (); uint used = 0; foreach (var use in specialUses) { var bit = (uint) (1 << ((int) use)); if ((used & bit) != 0) continue; used |= bit; 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.Important: uses.Append ("\\Important"); 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 encodedName = Engine.EncodeMailboxName (fullName); string command; if (uses.Length > 0) command = string.Format ("CREATE %S (USE ({0}))\r\n", uses); else command = "CREATE %S\r\n"; var ic = Engine.QueueCommand (cancellationToken, null, command, encodedName); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("CREATE", ic); var code = (MailboxIdResponseCode) GetResponseCode (ic, ImapResponseCodeType.MailboxId); var id = code?.MailboxId; var created = await GetCreatedFolderAsync (encodedName, id, true, doAsync, cancellationToken).ConfigureAwait (false); Engine.OnFolderCreated (created); return created; } /// /// Create a new subfolder with the given name. /// /// /// Creates a new subfolder with the given name. /// /// The created folder. /// The name of the folder to create. /// A list of special uses for the folder being created. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is nil, and thus child folders cannot be created. /// /// /// The IMAP server does not support the CREATE-SPECIAL-USE extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IMailFolder Create (string name, IEnumerable specialUses, CancellationToken cancellationToken = default (CancellationToken)) { return CreateAsync (name, specialUses, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously create a new subfolder with the given name. /// /// /// Creates a new subfolder with the given name. /// /// The created folder. /// The name of the folder to create. /// A list of special uses for the folder being created. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is empty. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is nil, and thus child folders cannot be created. /// /// /// The IMAP server does not support the CREATE-SPECIAL-USE extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task CreateAsync (string name, IEnumerable specialUses, CancellationToken cancellationToken = default (CancellationToken)) { return CreateAsync (name, specialUses, true, cancellationToken); } async Task RenameAsync (IMailFolder parent, string name, bool doAsync, CancellationToken cancellationToken) { if (parent == null) throw new ArgumentNullException (nameof (parent)); if (!(parent is ImapFolder) || ((ImapFolder) parent).Engine != Engine) throw new ArgumentException ("The parent folder does not belong to this ImapClient.", nameof (parent)); if (name == null) throw new ArgumentNullException (nameof (name)); if (!Engine.IsValidMailboxName (name, DirectorySeparator)) throw new ArgumentException ("The name is not a legal folder name.", nameof (name)); if (IsNamespace || (Attributes & FolderAttributes.Inbox) != 0) throw new InvalidOperationException ("Cannot rename this folder."); CheckState (false, false); string newFullName; if (!string.IsNullOrEmpty (parent.FullName)) newFullName = parent.FullName + parent.DirectorySeparator + name; else newFullName = name; var encodedName = Engine.EncodeMailboxName (newFullName); var ic = Engine.QueueCommand (cancellationToken, null, "RENAME %F %S\r\n", this, encodedName); var oldFullName = FullName; await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("RENAME", ic); Engine.FolderCache.Remove (EncodedName); Engine.FolderCache[encodedName] = this; ParentFolder = parent; FullName = Engine.DecodeMailboxName (encodedName); EncodedName = encodedName; Name = name; Reset (); if (Engine.Selected == this) { Engine.State = ImapEngineState.Authenticated; Engine.Selected = null; OnClosed (); } OnRenamed (oldFullName, FullName); } /// /// Rename the folder to exist with a new name under a new parent folder. /// /// /// Renames the folder to exist with a new name under a new parent folder. /// /// The new parent folder. /// The new name of the folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// does not belong to the . /// -or- /// is not a legal folder name. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The folder cannot be renamed (it is either a namespace or the Inbox). /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Rename (IMailFolder parent, string name, CancellationToken cancellationToken = default (CancellationToken)) { RenameAsync (parent, name, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously rename the folder to exist with a new name under a new parent folder. /// /// /// Renames the folder to exist with a new name under a new parent folder. /// /// An awaitable task. /// The new parent folder. /// The new name of the folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// does not belong to the . /// -or- /// is not a legal folder name. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The folder cannot be renamed (it is either a namespace or the Inbox). /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task RenameAsync (IMailFolder parent, string name, CancellationToken cancellationToken = default (CancellationToken)) { return RenameAsync (parent, name, true, cancellationToken); } async Task DeleteAsync (bool doAsync, CancellationToken cancellationToken) { if (IsNamespace || (Attributes & FolderAttributes.Inbox) != 0) throw new InvalidOperationException ("Cannot delete this folder."); CheckState (false, false); var ic = Engine.QueueCommand (cancellationToken, null, "DELETE %F\r\n", this); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("DELETE", ic); Reset (); if (Engine.Selected == this) { Engine.State = ImapEngineState.Authenticated; Engine.Selected = null; OnClosed (); } Attributes |= FolderAttributes.NonExistent; OnDeleted (); } /// /// Delete the folder on the IMAP server. /// /// /// Deletes the folder on the IMAP server. /// This method will not delete any child folders. /// /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The folder cannot be deleted (it is either a namespace or the Inbox). /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Delete (CancellationToken cancellationToken = default (CancellationToken)) { DeleteAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously delete the folder on the IMAP server. /// /// /// Deletes the folder on the IMAP server. /// This method will not delete any child folders. /// /// An awaitable task. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The folder cannot be deleted (it is either a namespace or the Inbox). /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task DeleteAsync (CancellationToken cancellationToken = default (CancellationToken)) { return DeleteAsync (true, cancellationToken); } async Task SubscribeAsync (bool doAsync, CancellationToken cancellationToken) { CheckState (false, false); var ic = Engine.QueueCommand (cancellationToken, null, "SUBSCRIBE %F\r\n", this); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SUBSCRIBE", ic); if ((Attributes & FolderAttributes.Subscribed) == 0) { Attributes |= FolderAttributes.Subscribed; OnSubscribed (); } } /// /// Subscribe the folder. /// /// /// Subscribes the folder. /// /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Subscribe (CancellationToken cancellationToken = default (CancellationToken)) { SubscribeAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously subscribe the folder. /// /// /// Subscribes the folder. /// /// An awaitable task. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task SubscribeAsync (CancellationToken cancellationToken = default (CancellationToken)) { return SubscribeAsync (true, cancellationToken); } async Task UnsubscribeAsync (bool doAsync, CancellationToken cancellationToken) { CheckState (false, false); var ic = Engine.QueueCommand (cancellationToken, null, "UNSUBSCRIBE %F\r\n", this); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("UNSUBSCRIBE", ic); if ((Attributes & FolderAttributes.Subscribed) != 0) { Attributes &= ~FolderAttributes.Subscribed; OnUnsubscribed (); } } /// /// Unsubscribe the folder. /// /// /// Unsubscribes the folder. /// /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Unsubscribe (CancellationToken cancellationToken = default (CancellationToken)) { UnsubscribeAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously unsubscribe the folder. /// /// /// Unsubscribes the folder. /// /// An awaitable task. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task UnsubscribeAsync (CancellationToken cancellationToken = default (CancellationToken)) { return UnsubscribeAsync (true, cancellationToken); } async Task> GetSubfoldersAsync (StatusItems items, bool subscribedOnly, bool doAsync, CancellationToken cancellationToken) { CheckState (false, false); // Note: folder names can contain wildcards (including '*' and '%'), so replace '*' with '%' // in order to reduce the list of folders returned by our LIST command. var pattern = new StringBuilder (EncodedName.Length + 2); pattern.Append (EncodedName); for (int i = 0; i < EncodedName.Length; i++) { if (pattern[i] == '*') pattern[i] = '%'; } if (pattern.Length > 0) pattern.Append (DirectorySeparator); pattern.Append ('%'); var children = new List (); var status = items != StatusItems.None; var list = new List (); var command = new StringBuilder (); var returnsSubscribed = false; var lsub = subscribedOnly; if (subscribedOnly) { if ((Engine.Capabilities & ImapCapabilities.ListExtended) != 0) { command.Append ("LIST (SUBSCRIBED)"); returnsSubscribed = true; 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 "); returnsSubscribed = true; } 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 "); returnsSubscribed = true; } command.Append ("CHILDREN"); command.Append (')'); } } command.Append ("\r\n"); var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), pattern.ToString ()); ic.RegisterUntaggedHandler (lsub ? "LSUB" : "LIST", ImapUtils.ParseFolderListAsync); ic.ListReturnsSubscribed = returnsSubscribed; ic.UserData = list; ic.Lsub = lsub; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); // Note: Due to the fact that folders can contain wildcards in them, we'll need to // filter out any folders that are not children of this folder. var prefix = FullName.Length > 0 ? FullName + DirectorySeparator : string.Empty; prefix = ImapUtils.CanonicalizeMailboxName (prefix, DirectorySeparator); var unparented = false; foreach (var folder in list) { var canonicalFullName = ImapUtils.CanonicalizeMailboxName (folder.FullName, folder.DirectorySeparator); var canonicalName = ImapUtils.IsInbox (folder.FullName) ? "INBOX" : folder.Name; if (!canonicalFullName.StartsWith (prefix, StringComparison.Ordinal)) { unparented |= folder.ParentFolder == null; continue; } if (string.Compare (canonicalFullName, prefix.Length, canonicalName, 0, canonicalName.Length, StringComparison.Ordinal) != 0) { unparented |= folder.ParentFolder == null; continue; } folder.ParentFolder = this; children.Add (folder); } ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create (lsub ? "LSUB" : "LIST", ic); // Note: if any folders returned in the LIST command are unparented, have the ImapEngine look up their // parent folders now so that they are not left in an inconsistent state. if (unparented) await Engine.LookupParentFoldersAsync (list, doAsync, cancellationToken).ConfigureAwait (false); if (status) { for (int i = 0; i < children.Count; i++) { if (children[i].Exists) await ((ImapFolder) children[i]).StatusAsync (items, doAsync, false, cancellationToken).ConfigureAwait (false); } } return children; } /// /// Get the subfolders. /// /// /// Gets the subfolders. /// /// The subfolders. /// The status items to pre-populate. /// If set to true, only subscribed folders will be listed. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList GetSubfolders (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken)) { return GetSubfoldersAsync (items, subscribedOnly, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the subfolders. /// /// /// Gets the subfolders. /// /// The subfolders. /// The status items to pre-populate. /// If set to true, only subscribed folders will be listed. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> GetSubfoldersAsync (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken)) { return GetSubfoldersAsync (items, subscribedOnly, true, cancellationToken); } async Task GetSubfolderAsync (string name, bool doAsync, CancellationToken cancellationToken) { if (name == null) throw new ArgumentNullException (nameof (name)); if (!Engine.IsValidMailboxName (name, DirectorySeparator)) throw new ArgumentException ("The name of the subfolder is invalid.", nameof (name)); CheckState (false, false); var fullName = FullName.Length > 0 ? FullName + DirectorySeparator + name : name; var encodedName = Engine.EncodeMailboxName (fullName); List list; ImapFolder folder; if (Engine.GetCachedFolder (encodedName, out folder)) return folder; // Note: folder names can contain wildcards (including '*' and '%'), so replace '*' with '%' // in order to reduce the list of folders returned by our LIST command. var pattern = encodedName.Replace ('*', '%'); var ic = new ImapCommand (Engine, cancellationToken, null, "LIST \"\" %S\r\n", pattern); ic.RegisterUntaggedHandler ("LIST", ImapUtils.ParseFolderListAsync); ic.UserData = list = new List (); Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("LIST", ic); if ((folder = ImapEngine.GetFolder (list, encodedName)) != null) folder.ParentFolder = this; if (list.Count > 1 || folder == null) { // Note: if any folders returned in the LIST command are unparented, have the ImapEngine look up their // parent folders now so that they are not left in an inconsistent state. await Engine.LookupParentFoldersAsync (list, doAsync, cancellationToken).ConfigureAwait (false); } if (folder == null) throw new FolderNotFoundException (fullName); return folder; } /// /// Get the specified subfolder. /// /// /// Gets the specified subfolder. /// /// The subfolder. /// The name of the subfolder. /// The cancellation token. /// /// is null. /// /// /// is either an empty string or contains the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The requested folder could not be found. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IMailFolder GetSubfolder (string name, CancellationToken cancellationToken = default (CancellationToken)) { return GetSubfolderAsync (name, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified subfolder. /// /// /// Gets the specified subfolder. /// /// The subfolder. /// The name of the subfolder. /// The cancellation token. /// /// is null. /// /// /// is either an empty string or contains the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The requested folder could not be found. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetSubfolderAsync (string name, CancellationToken cancellationToken = default (CancellationToken)) { return GetSubfolderAsync (name, true, cancellationToken); } async Task CheckAsync (bool doAsync, CancellationToken cancellationToken) { CheckState (true, false); var ic = Engine.QueueCommand (cancellationToken, this, "CHECK\r\n"); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("CHECK", ic); } /// /// Force the server to sync its in-memory state with its disk state. /// /// /// The CHECK command forces the IMAP server to sync its /// in-memory state with its disk state. /// For more information about the CHECK command, see /// rfc350101. /// /// The cancellation token. /// /// The has been disposed. /// /// /// The is not currently open. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Check (CancellationToken cancellationToken = default (CancellationToken)) { CheckAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously force the server to sync its in-memory state with its disk state. /// /// /// The CHECK command forces the IMAP server to sync its /// in-memory state with its disk state. /// For more information about the CHECK command, see /// rfc350101. /// /// An awaitable task. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not currently open. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task CheckAsync (CancellationToken cancellationToken = default (CancellationToken)) { return CheckAsync (true, cancellationToken); } internal async Task StatusAsync (StatusItems items, bool doAsync, bool throwNotFound, CancellationToken cancellationToken) { if ((Engine.Capabilities & ImapCapabilities.Status) == 0) throw new NotSupportedException ("The IMAP server does not support the STATUS command."); CheckState (false, false); if (items == StatusItems.None) return; var command = string.Format ("STATUS %F ({0})\r\n", Engine.GetStatusQuery (items)); var ic = Engine.QueueCommand (cancellationToken, null, command, this); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, this, throwNotFound); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("STATUS", ic); } /// /// Update the values of the specified items. /// /// /// Updates the values of the specified items. /// The method /// MUST NOT be used on a folder that is already in the opened state. Instead, other ways /// of getting the desired information should be used. /// For example, a common use for the /// method is to get the number of unread messages in the folder. When the folder is open, however, it is /// possible to use the /// method to query for the list of unread messages. /// For more information about the STATUS command, see /// rfc3501. /// /// The items to update. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The IMAP server does not support the STATUS command. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Status (StatusItems items, CancellationToken cancellationToken = default (CancellationToken)) { StatusAsync (items, false, true, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously update the values of the specified items. /// /// /// Updates the values of the specified items. /// The method /// MUST NOT be used on a folder that is already in the opened state. Instead, other ways /// of getting the desired information should be used. /// For example, a common use for the /// method is to get the number of unread messages in the folder. When the folder is open, however, it is /// possible to use the /// method to query for the list of unread messages. /// For more information about the STATUS command, see /// rfc3501. /// /// An awaitable task. /// The items to update. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The does not exist. /// /// /// The IMAP server does not support the STATUS command. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task StatusAsync (StatusItems items, CancellationToken cancellationToken = default (CancellationToken)) { return StatusAsync (items, true, true, cancellationToken); } static async Task ReadStringTokenAsync (ImapEngine engine, string format, bool doAsync, CancellationToken cancellationToken) { var token = await engine.ReadTokenAsync (ImapStream.AtomSpecials, doAsync, cancellationToken).ConfigureAwait (false); switch (token.Type) { case ImapTokenType.Literal: return await engine.ReadLiteralAsync (doAsync, cancellationToken).ConfigureAwait (false); case ImapTokenType.QString: return (string) token.Value; case ImapTokenType.Atom: return (string) token.Value; default: throw ImapEngine.UnexpectedToken (format, token); } } static async Task UntaggedAclAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { string format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "ACL", "{0}"); var acl = (AccessControlList) ic.UserData; string name, rights; ImapToken token; // read the mailbox name await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); do { name = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); rights = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); acl.Add (new AccessControl (name, rights)); token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); } while (token.Type != ImapTokenType.Eoln); } async Task GetAccessControlListAsync (bool doAsync, CancellationToken cancellationToken) { if ((Engine.Capabilities & ImapCapabilities.Acl) == 0) throw new NotSupportedException ("The IMAP server does not support the ACL extension."); CheckState (false, false); var ic = new ImapCommand (Engine, cancellationToken, null, "GETACL %F\r\n", this); ic.RegisterUntaggedHandler ("ACL", UntaggedAclAsync); ic.UserData = new AccessControlList (); Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("GETACL", ic); return (AccessControlList) ic.UserData; } /// /// Get the complete access control list for the folder. /// /// /// Gets the complete access control list for the folder. /// /// The access control list. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override AccessControlList GetAccessControlList (CancellationToken cancellationToken = default (CancellationToken)) { return GetAccessControlListAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the complete access control list for the folder. /// /// /// Gets the complete access control list for the folder. /// /// The access control list. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override Task GetAccessControlListAsync (CancellationToken cancellationToken = default (CancellationToken)) { return GetAccessControlListAsync (true, cancellationToken); } static async Task UntaggedListRightsAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { string format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "LISTRIGHTS", "{0}"); var access = (AccessRights) ic.UserData; ImapToken token; // read the mailbox name await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); // read the identity name await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); do { var rights = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); access.AddRange (rights); token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); } while (token.Type != ImapTokenType.Eoln); } async Task GetAccessRightsAsync (string name, bool doAsync, CancellationToken cancellationToken) { if (name == null) throw new ArgumentNullException (nameof (name)); if ((Engine.Capabilities & ImapCapabilities.Acl) == 0) throw new NotSupportedException ("The IMAP server does not support the ACL extension."); CheckState (false, false); var ic = new ImapCommand (Engine, cancellationToken, null, "LISTRIGHTS %F %S\r\n", this, name); ic.RegisterUntaggedHandler ("LISTRIGHTS", UntaggedListRightsAsync); ic.UserData = new AccessRights (); Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("LISTRIGHTS", ic); return (AccessRights) ic.UserData; } /// /// Get the access rights for a particular identifier. /// /// /// Gets the access rights for a particular identifier. /// /// The access rights. /// The identifier name. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override AccessRights GetAccessRights (string name, CancellationToken cancellationToken = default (CancellationToken)) { return GetAccessRightsAsync (name, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the access rights for a particular identifier. /// /// /// Gets the access rights for a particular identifier. /// /// The access rights. /// The identifier name. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override Task GetAccessRightsAsync (string name, CancellationToken cancellationToken = default (CancellationToken)) { return GetAccessRightsAsync (name, true, cancellationToken); } static async Task UntaggedMyRightsAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { string format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "MYRIGHTS", "{0}"); var access = (AccessRights) ic.UserData; // read the mailbox name await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); // read the access rights access.AddRange (await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false)); } async Task GetMyAccessRightsAsync (bool doAsync, CancellationToken cancellationToken) { if ((Engine.Capabilities & ImapCapabilities.Acl) == 0) throw new NotSupportedException ("The IMAP server does not support the ACL extension."); CheckState (false, false); var ic = new ImapCommand (Engine, cancellationToken, null, "MYRIGHTS %F\r\n", this); ic.RegisterUntaggedHandler ("MYRIGHTS", UntaggedMyRightsAsync); ic.UserData = new AccessRights (); Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("MYRIGHTS", ic); return (AccessRights) ic.UserData; } /// /// Get the access rights for the current authenticated user. /// /// /// Gets the access rights for the current authenticated user. /// /// The access rights. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override AccessRights GetMyAccessRights (CancellationToken cancellationToken = default (CancellationToken)) { return GetMyAccessRightsAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the access rights for the current authenticated user. /// /// /// Gets the access rights for the current authenticated user. /// /// The access rights. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override Task GetMyAccessRightsAsync (CancellationToken cancellationToken = default (CancellationToken)) { return GetMyAccessRightsAsync (true, cancellationToken); } async Task ModifyAccessRightsAsync (string name, AccessRights rights, string action, bool doAsync, CancellationToken cancellationToken) { if ((Engine.Capabilities & ImapCapabilities.Acl) == 0) throw new NotSupportedException ("The IMAP server does not support the ACL extension."); CheckState (false, false); var ic = Engine.QueueCommand (cancellationToken, null, "SETACL %F %S %S\r\n", this, name, action + rights); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SETACL", ic); } /// /// Add access rights for the specified identity. /// /// /// Adds the given access rights for the specified identity. /// /// The identity name. /// The access rights. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// No rights were specified. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override void AddAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException (nameof (name)); if (rights == null) throw new ArgumentNullException (nameof (rights)); if (rights.Count == 0) throw new ArgumentException ("No rights were specified.", nameof (rights)); ModifyAccessRightsAsync (name, rights, "+", false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously add access rights for the specified identity. /// /// /// Adds the given access rights for the specified identity. /// /// An asynchronous task context. /// The identity name. /// The access rights. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// No rights were specified. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override Task AddAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException (nameof (name)); if (rights == null) throw new ArgumentNullException (nameof (rights)); if (rights.Count == 0) throw new ArgumentException ("No rights were specified.", nameof (rights)); return ModifyAccessRightsAsync (name, rights, "+", true, cancellationToken); } /// /// Remove access rights for the specified identity. /// /// /// Removes the given access rights for the specified identity. /// /// The identity name. /// The access rights. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// No rights were specified. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override void RemoveAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException (nameof (name)); if (rights == null) throw new ArgumentNullException (nameof (rights)); if (rights.Count == 0) throw new ArgumentException ("No rights were specified.", nameof (rights)); ModifyAccessRightsAsync (name, rights, "-", false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously remove access rights for the specified identity. /// /// /// Removes the given access rights for the specified identity. /// /// An asynchronous task context. /// The identity name. /// The access rights. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// No rights were specified. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override Task RemoveAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException (nameof (name)); if (rights == null) throw new ArgumentNullException (nameof (rights)); if (rights.Count == 0) throw new ArgumentException ("No rights were specified.", nameof (rights)); return ModifyAccessRightsAsync (name, rights, "-", true, cancellationToken); } /// /// Set the access rights for the specified identity. /// /// /// Sets the access rights for the specified identity. /// /// The identity name. /// The access rights. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override void SetAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException (nameof (name)); if (rights == null) throw new ArgumentNullException (nameof (rights)); ModifyAccessRightsAsync (name, rights, string.Empty, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the access rights for the specified identity. /// /// /// Sets the access rights for the specified identity. /// /// An awaitable task. /// The identity name. /// The access rights. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override Task SetAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken)) { if (name == null) throw new ArgumentNullException (nameof (name)); if (rights == null) throw new ArgumentNullException (nameof (rights)); return ModifyAccessRightsAsync (name, rights, string.Empty, true, cancellationToken); } async Task RemoveAccessAsync (string name, bool doAsync, CancellationToken cancellationToken) { if (name == null) throw new ArgumentNullException (nameof (name)); if ((Engine.Capabilities & ImapCapabilities.Acl) == 0) throw new NotSupportedException ("The IMAP server does not support the ACL extension."); CheckState (false, false); var ic = Engine.QueueCommand (cancellationToken, null, "DELETEACL %F %S\r\n", this, name); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("DELETEACL", ic); } /// /// Remove all access rights for the given identity. /// /// /// Removes all access rights for the given identity. /// /// The identity name. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override void RemoveAccess (string name, CancellationToken cancellationToken = default (CancellationToken)) { RemoveAccessAsync (name, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously remove all access rights for the given identity. /// /// /// Removes all access rights for the given identity. /// /// An awaitable task. /// The identity name. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the ACL extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The command failed. /// public override Task RemoveAccessAsync (string name, CancellationToken cancellationToken = default (CancellationToken)) { return RemoveAccessAsync (name, true, cancellationToken); } async Task GetMetadataAsync (MetadataTag tag, bool doAsync, CancellationToken cancellationToken) { CheckState (false, false); if ((Engine.Capabilities & ImapCapabilities.Metadata) == 0) throw new NotSupportedException ("The IMAP server does not support the METADATA extension."); var ic = new ImapCommand (Engine, cancellationToken, null, "GETMETADATA %F %S\r\n", this, tag.Id); ic.RegisterUntaggedHandler ("METADATA", ImapUtils.ParseMetadataAsync); var metadata = new MetadataCollection (); ic.UserData = metadata; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("GETMETADATA", ic); string value = null; for (int i = 0; i < metadata.Count; i++) { if (metadata[i].EncodedName == EncodedName && metadata[i].Tag.Id == tag.Id) { value = metadata[i].Value; metadata.RemoveAt (i); break; } } Engine.ProcessMetadataChanges (metadata); return value; } /// /// Get the specified metadata. /// /// /// Gets the specified metadata. /// /// The requested metadata value. /// The metadata tag. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the METADATA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override string GetMetadata (MetadataTag tag, CancellationToken cancellationToken = default (CancellationToken)) { return GetMetadataAsync (tag, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified metadata. /// /// /// Gets the specified metadata. /// /// The requested metadata value. /// The metadata tag. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the METADATA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetMetadataAsync (MetadataTag tag, CancellationToken cancellationToken = default (CancellationToken)) { return GetMetadataAsync (tag, true, cancellationToken); } async Task GetMetadataAsync (MetadataOptions options, IEnumerable tags, bool doAsync, CancellationToken cancellationToken) { if (options == null) throw new ArgumentNullException (nameof (options)); if (tags == null) throw new ArgumentNullException (nameof (tags)); 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 ("GETMETADATA %F"); var args = new List (); bool hasOptions = false; if (options.MaxSize.HasValue || options.Depth != 0) { command.Append (" ("); if (options.MaxSize.HasValue) command.AppendFormat ("MAXSIZE {0} ", options.MaxSize.Value); if (options.Depth > 0) command.AppendFormat ("DEPTH {0} ", options.Depth == int.MaxValue ? "infinity" : "1"); command[command.Length - 1] = ')'; command.Append (' '); hasOptions = true; } args.Add (this); int startIndex = command.Length; foreach (var tag in tags) { command.Append (" %S"); args.Add (tag.Id); } if (hasOptions) { command[startIndex] = '('; command.Append (')'); } command.Append ("\r\n"); if (args.Count == 1) return new MetadataCollection (); var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), args.ToArray ()); ic.RegisterUntaggedHandler ("METADATA", ImapUtils.ParseMetadataAsync); ic.UserData = new MetadataCollection (); options.LongEntries = 0; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("GETMETADATA", ic); var metadata = (MetadataResponseCode) GetResponseCode (ic, ImapResponseCodeType.Metadata); if (metadata != null && metadata.SubType == MetadataResponseCodeSubType.LongEntries) options.LongEntries = metadata.Value; return Engine.FilterMetadata ((MetadataCollection) ic.UserData, EncodedName); } /// /// Get the specified metadata. /// /// /// Gets the specified metadata. /// /// The requested metadata. /// The metadata options. /// The metadata tags. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the METADATA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override MetadataCollection GetMetadata (MetadataOptions options, IEnumerable tags, CancellationToken cancellationToken = default (CancellationToken)) { return GetMetadataAsync (options, tags, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the specified metadata. /// /// /// Gets the specified metadata. /// /// The requested metadata. /// The metadata options. /// The metadata tags. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the METADATA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetMetadataAsync (MetadataOptions options, IEnumerable tags, CancellationToken cancellationToken = default (CancellationToken)) { return GetMetadataAsync (options, tags, true, cancellationToken); } async Task SetMetadataAsync (MetadataCollection metadata, bool doAsync, CancellationToken cancellationToken) { if (metadata == null) throw new ArgumentNullException (nameof (metadata)); CheckState (false, false); if ((Engine.Capabilities & ImapCapabilities.Metadata) == 0) throw new NotSupportedException ("The IMAP server does not support the METADATA extension."); if (metadata.Count == 0) return; var command = new StringBuilder ("SETMETADATA %F ("); var args = new List (); args.Add (this); for (int i = 0; i < metadata.Count; i++) { if (i > 0) command.Append (' '); if (metadata[i].Value != null) { command.Append ("%S %S"); args.Add (metadata[i].Tag.Id); args.Add (metadata[i].Value); } else { command.Append ("%S NIL"); args.Add (metadata[i].Tag.Id); } } command.Append (")\r\n"); var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), args.ToArray ()); Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SETMETADATA", ic); } /// /// Set the specified metadata. /// /// /// Sets the specified metadata. /// /// The metadata. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the METADATA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void SetMetadata (MetadataCollection metadata, CancellationToken cancellationToken = default (CancellationToken)) { SetMetadataAsync (metadata, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously set the specified metadata. /// /// /// Sets the specified metadata. /// /// An asynchronous task context. /// The metadata. /// The cancellation token. /// /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the METADATA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task SetMetadataAsync (MetadataCollection metadata, CancellationToken cancellationToken = default (CancellationToken)) { return SetMetadataAsync (metadata, true, cancellationToken); } class Quota { public uint? MessageLimit; public uint? StorageLimit; public uint? CurrentMessageCount; public uint? CurrentStorageSize; } class QuotaContext { public QuotaContext () { Quotas = new Dictionary (); QuotaRoots = new List (); } public IList QuotaRoots { get; private set; } public IDictionary Quotas { get; private set; } } static async Task UntaggedQuotaRootAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { var format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "QUOTAROOT", "{0}"); var ctx = (QuotaContext) ic.UserData; // The first token should be the mailbox name await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); // ...followed by 0 or more quota roots var token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); while (token.Type != ImapTokenType.Eoln) { var root = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); ctx.QuotaRoots.Add (root); token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); } } static async Task UntaggedQuotaAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync) { var format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "QUOTA", "{0}"); var quotaRoot = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false); var ctx = (QuotaContext) ic.UserData; var quota = new Quota (); var token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.OpenParen, format, token); while (token.Type != ImapTokenType.CloseParen) { uint used, limit; string resource; token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); ImapEngine.AssertToken (token, ImapTokenType.Atom, format, token); resource = (string) token.Value; token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); used = ImapEngine.ParseNumber (token, false, format, token); token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); limit = ImapEngine.ParseNumber (token, false, format, token); switch (resource.ToUpperInvariant ()) { case "MESSAGE": quota.CurrentMessageCount = used; quota.MessageLimit = limit; break; case "STORAGE": quota.CurrentStorageSize = used; quota.StorageLimit = limit; break; } token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); } // read the closing paren await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false); ctx.Quotas[quotaRoot] = quota; } async Task GetQuotaAsync (bool doAsync, CancellationToken cancellationToken) { CheckState (false, false); if ((Engine.Capabilities & ImapCapabilities.Quota) == 0) throw new NotSupportedException ("The IMAP server does not support the QUOTA extension."); var ic = new ImapCommand (Engine, cancellationToken, null, "GETQUOTAROOT %F\r\n", this); var ctx = new QuotaContext (); ic.RegisterUntaggedHandler ("QUOTAROOT", UntaggedQuotaRootAsync); ic.RegisterUntaggedHandler ("QUOTA", UntaggedQuotaAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("GETQUOTAROOT", ic); for (int i = 0; i < ctx.QuotaRoots.Count; i++) { var encodedName = ctx.QuotaRoots[i]; ImapFolder quotaRoot; Quota quota; if (!ctx.Quotas.TryGetValue (encodedName, out quota)) continue; quotaRoot = await Engine.GetQuotaRootFolderAsync (encodedName, doAsync, cancellationToken).ConfigureAwait (false); return new FolderQuota (quotaRoot) { CurrentMessageCount = quota.CurrentMessageCount, CurrentStorageSize = quota.CurrentStorageSize, MessageLimit = quota.MessageLimit, StorageLimit = quota.StorageLimit }; } return new FolderQuota (null); } /// /// Get the quota information for the folder. /// /// /// Gets the quota information for the folder. /// To determine if a quotas are supported, check the /// property. /// /// The folder quota. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the QUOTA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override FolderQuota GetQuota (CancellationToken cancellationToken = default (CancellationToken)) { return GetQuotaAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously get the quota information for the folder. /// /// /// Gets the quota information for the folder. /// To determine if a quotas are supported, check the /// property. /// /// The folder quota. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the QUOTA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task GetQuotaAsync (CancellationToken cancellationToken = default (CancellationToken)) { return GetQuotaAsync (true, cancellationToken); } async Task SetQuotaAsync (uint? messageLimit, uint? storageLimit, bool doAsync, CancellationToken cancellationToken) { CheckState (false, false); if ((Engine.Capabilities & ImapCapabilities.Quota) == 0) throw new NotSupportedException ("The IMAP server does not support the QUOTA extension."); var command = new StringBuilder ("SETQUOTA %F ("); if (messageLimit.HasValue) command.AppendFormat ("MESSAGE {0} ", messageLimit.Value); if (storageLimit.HasValue) command.AppendFormat ("STORAGE {0} ", storageLimit.Value); command[command.Length - 1] = ')'; command.Append ("\r\n"); var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), this); var ctx = new QuotaContext (); Quota quota; ic.RegisterUntaggedHandler ("QUOTA", UntaggedQuotaAsync); ic.UserData = ctx; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SETQUOTA", ic); if (ctx.Quotas.TryGetValue (EncodedName, out quota)) { return new FolderQuota (this) { CurrentMessageCount = quota.CurrentMessageCount, CurrentStorageSize = quota.CurrentStorageSize, MessageLimit = quota.MessageLimit, StorageLimit = quota.StorageLimit }; } return new FolderQuota (null); } /// /// Set the quota limits for the folder. /// /// /// Sets the quota limits for the folder. /// To determine if a quotas are supported, check the /// property. /// /// The folder quota. /// If not null, sets the maximum number of messages to allow. /// If not null, sets the maximum storage size (in kilobytes). /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the QUOTA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override FolderQuota SetQuota (uint? messageLimit, uint? storageLimit, CancellationToken cancellationToken = default (CancellationToken)) { return SetQuotaAsync (messageLimit, storageLimit, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously set the quota limits for the folder. /// /// /// Sets the quota limits for the folder. /// To determine if a quotas are supported, check the /// property. /// /// The folder quota. /// If not null, sets the maximum number of messages to allow. /// If not null, sets the maximum storage size (in kilobytes). /// The cancellation token. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The IMAP server does not support the QUOTA extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task SetQuotaAsync (uint? messageLimit, uint? storageLimit, CancellationToken cancellationToken = default (CancellationToken)) { return SetQuotaAsync (messageLimit, storageLimit, true, cancellationToken); } async Task ExpungeAsync (bool doAsync, CancellationToken cancellationToken) { CheckState (true, true); var ic = Engine.QueueCommand (cancellationToken, this, "EXPUNGE\r\n"); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("EXPUNGE", ic); } /// /// Expunge the folder, permanently removing all messages marked for deletion. /// /// /// The EXPUNGE command permanently removes all messages in the folder /// that have the flag set. /// For more information about the EXPUNGE command, see /// rfc3501. /// Normally, a event will be emitted /// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension /// and it has been enabled via the /// method, then the event will be emitted rather than /// the event. /// /// The cancellation token. /// /// The has been disposed. /// /// /// The is not currently open in read-write mode. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Expunge (CancellationToken cancellationToken = default (CancellationToken)) { ExpungeAsync (false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously expunge the folder, permanently removing all messages marked for deletion. /// /// /// The EXPUNGE command permanently removes all messages in the folder /// that have the flag set. /// For more information about the EXPUNGE command, see /// rfc3501. /// Normally, a event will be emitted /// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension /// and it has been enabled via the /// method, then the event will be emitted rather than /// the event. /// /// An asynchronous task context. /// The cancellation token. /// /// The has been disposed. /// /// /// The is not currently open in read-write mode. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task ExpungeAsync (CancellationToken cancellationToken = default (CancellationToken)) { return ExpungeAsync (true, cancellationToken); } async Task ExpungeAsync (IList uids, bool doAsync, CancellationToken cancellationToken) { if (uids == null) throw new ArgumentNullException (nameof (uids)); CheckState (true, true); if (uids.Count == 0) return; if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { // get the list of messages marked for deletion that should not be expunged var query = SearchQuery.Deleted.And (SearchQuery.Not (SearchQuery.Uids (uids))); var unmark = await SearchAsync (query, doAsync, false, cancellationToken).ConfigureAwait (false); if (unmark.Count > 0) { // clear the \Deleted flag on all messages except the ones that are to be expunged await ModifyFlagsAsync (unmark, null, MessageFlags.Deleted, null, "-FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false); } // expunge the folder await ExpungeAsync (doAsync, cancellationToken).ConfigureAwait (false); if (unmark.Count > 0) { // restore the \Deleted flags await ModifyFlagsAsync (unmark, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false); } return; } foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID EXPUNGE %s\r\n", uids)) { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("EXPUNGE", ic); } } /// /// Expunge the specified uids, permanently removing them from the folder. /// /// /// Expunges the specified uids, permanently removing them from the folder. /// If the IMAP server supports the UIDPLUS extension (check the /// for the /// flag), then this operation is atomic. Otherwise, MailKit implements this operation /// by first searching for the full list of message uids in the folder that are marked for /// deletion, unmarking the set of message uids that are not within the specified list of /// uids to be be expunged, expunging the folder (thus expunging the requested uids), and /// finally restoring the deleted flag on the collection of message uids that were originally /// marked for deletion that were not included in the list of uids provided. For this reason, /// it is advisable for clients that wish to maintain state to implement this themselves when /// the IMAP server does not support the UIDPLUS extension. /// For more information about the UID EXPUNGE command, see /// rfc4315. /// Normally, a event will be emitted /// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension /// and it has been enabled via the /// method, then the event will be emitted rather than /// the event. /// /// The message uids. /// The cancellation token. /// /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void Expunge (IList uids, CancellationToken cancellationToken = default (CancellationToken)) { ExpungeAsync (uids, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously expunge the specified uids, permanently removing them from the folder. /// /// /// Expunges the specified uids, permanently removing them from the folder. /// If the IMAP server supports the UIDPLUS extension (check the /// for the /// flag), then this operation is atomic. Otherwise, MailKit implements this operation /// by first searching for the full list of message uids in the folder that are marked for /// deletion, unmarking the set of message uids that are not within the specified list of /// uids to be be expunged, expunging the folder (thus expunging the requested uids), and /// finally restoring the deleted flag on the collection of message uids that were originally /// marked for deletion that were not included in the list of uids provided. For this reason, /// it is advisable for clients that wish to maintain state to implement this themselves when /// the IMAP server does not support the UIDPLUS extension. /// For more information about the UID EXPUNGE command, see /// rfc4315. /// Normally, a event will be emitted /// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension /// and it has been enabled via the /// method, then the event will be emitted rather than /// the event. /// /// An asynchronous task context. /// The message uids. /// The cancellation token. /// /// is null. /// /// /// One or more of the is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task ExpungeAsync (IList uids, CancellationToken cancellationToken = default (CancellationToken)) { return ExpungeAsync (uids, true, cancellationToken); } ImapCommand QueueAppend (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, CancellationToken cancellationToken, ITransferProgress progress) { var builder = new StringBuilder ("APPEND %F "); var list = new List (); list.Add (this); if ((flags & SettableFlags) != 0) builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags, 0)); if (date.HasValue) builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (date.Value)); if (annotations != null && annotations.Count > 0) { ImapUtils.FormatAnnotations (builder, annotations, list, false); if (builder[builder.Length - 1] != ' ') builder.Append (' '); } builder.Append ("%L\r\n"); list.Add (message); var command = builder.ToString (); var args = list.ToArray (); var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args); ic.Progress = progress; Engine.QueueCommand (ic); return ic; } async Task AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (options == null) throw new ArgumentNullException (nameof (options)); if (message == null) throw new ArgumentNullException (nameof (message)); CheckState (false, false); if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); var format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; format.EnsureNewLine = true; if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) format.International = true; if (format.International && !Engine.UTF8Enabled) throw new InvalidOperationException ("The UTF8 extension has not been enabled."); var ic = QueueAppend (format, message, flags, date, annotations, cancellationToken, progress); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("APPEND", ic); var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid); if (append != null) return append.UidSet[0]; return null; } /// /// Append the specified message to the folder. /// /// /// Appends the specified message to the folder and returns the UniqueId assigned to the message. /// /// The UID of the appended message, if available; otherwise, null. /// The formatting options. /// The message. /// The message flags. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueId? Append (FormatOptions options, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, message, flags, null, null, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously append the specified message to the folder. /// /// /// Appends the specified message to the folder and returns the UniqueId assigned to the message. /// /// The UID of the appended message, if available; otherwise, null. /// The formatting options. /// The message. /// The message flags. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, message, flags, null, null, true, cancellationToken, progress); } /// /// Append the specified message to the folder. /// /// /// Appends the specified message to the folder and returns the UniqueId assigned to the message. /// /// The UID of the appended message, if available; otherwise, null. /// The formatting options. /// The message. /// The message flags. /// The received date of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueId? Append (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, message, flags, date, null, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously append the specified message to the folder. /// /// /// Appends the specified message to the folder and returns the UniqueId assigned to the message. /// /// The UID of the appended message, if available; otherwise, null. /// The formatting options. /// The message. /// The message flags. /// The received date of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, message, flags, date, null, true, cancellationToken, progress); } /// /// Append the specified message to the folder. /// /// /// Appends the specified message to the folder and returns the UniqueId assigned to the message. /// /// The UID of the appended message, if available; otherwise, null. /// The formatting options. /// The message. /// The message flags. /// The received date of the message. /// The message annotations. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueId? Append (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, message, flags, date, annotations, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously append the specified message to the folder. /// /// /// Appends the specified message to the folder and returns the UniqueId assigned to the message. /// /// The UID of the appended message, if available; otherwise, null. /// The formatting options. /// The message. /// The message flags. /// The received date of the message. /// The message annotations. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, message, flags, date, annotations, true, cancellationToken, progress); } ImapCommand QueueMultiAppend (FormatOptions options, IList messages, IList flags, IList dates, IList> annotations, CancellationToken cancellationToken, ITransferProgress progress) { var builder = new StringBuilder ("APPEND %F"); var list = new List (); list.Add (this); for (int i = 0; i < messages.Count; i++) { builder.Append (' '); if ((flags[i] & SettableFlags) != 0) builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags[i], 0)); if (dates != null) builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (dates[i])); //if (annotations != null && annotations[i] != null && annotations[i].Count > 0) { // ImapUtils.FormatAnnotations (builder, annotations[i], list, false); // if (builder[builder.Length - 1] != ' ') // builder.Append (' '); //} builder.Append ("%L"); list.Add (messages[i]); } builder.Append ("\r\n"); var command = builder.ToString (); var args = list.ToArray (); var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args); ic.Progress = progress; Engine.QueueCommand (ic); return ic; } async Task> AppendAsync (FormatOptions options, IList messages, IList flags, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (options == null) throw new ArgumentNullException (nameof (options)); if (messages == null) throw new ArgumentNullException (nameof (messages)); for (int i = 0; i < messages.Count; i++) { if (messages[i] == null) throw new ArgumentException ("One or more of the messages is null."); } if (flags == null) throw new ArgumentNullException (nameof (flags)); if (messages.Count != flags.Count) throw new ArgumentException ("The number of messages and the number of flags must be equal."); CheckState (false, false); if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); var format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; format.EnsureNewLine = true; if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) format.International = true; if (format.International && !Engine.UTF8Enabled) throw new InvalidOperationException ("The UTF8 extension has not been enabled."); if (messages.Count == 0) return new UniqueId[0]; if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) { var ic = QueueMultiAppend (format, messages, flags, null, null, cancellationToken, progress); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("APPEND", ic); var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid); if (append != null) return append.UidSet; return new UniqueId[0]; } // FIXME: use an aggregate progress reporter var uids = new List (); for (int i = 0; i < messages.Count; i++) { var uid = await AppendAsync (format, messages[i], flags[i], null, null, doAsync, cancellationToken, progress).ConfigureAwait (false); if (uids != null && uid.HasValue) uids.Add (uid.Value); else uids = null; } if (uids == null) return new UniqueId[0]; return uids; } /// /// Append the specified messages to the folder. /// /// /// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages. /// /// The UIDs of the appended messages, if available; otherwise an empty array. /// The formatting options. /// The list of messages to append to the folder. /// The message flags to use for each message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// -or- /// is null. /// /// /// One or more of the is null. /// -or- /// The number of messages does not match the number of flags. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Append (FormatOptions options, IList messages, IList flags, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, messages, flags, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously append the specified messages to the folder. /// /// /// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages. /// /// The UIDs of the appended messages, if available; otherwise an empty array. /// The formatting options. /// The list of messages to append to the folder. /// The message flags to use for each message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// -or- /// is null. /// /// /// One or more of the is null. /// -or- /// The number of messages does not match the number of flags. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> AppendAsync (FormatOptions options, IList messages, IList flags, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, messages, flags, true, cancellationToken, progress); } async Task> AppendAsync (FormatOptions options, IList messages, IList flags, IList dates, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (options == null) throw new ArgumentNullException (nameof (options)); if (messages == null) throw new ArgumentNullException (nameof (messages)); for (int i = 0; i < messages.Count; i++) { if (messages[i] == null) throw new ArgumentException ("One or more of the messages is null."); } if (flags == null) throw new ArgumentNullException (nameof (flags)); if (dates == null) throw new ArgumentNullException (nameof (dates)); if (messages.Count != flags.Count || messages.Count != dates.Count) throw new ArgumentException ("The number of messages, the number of flags, and the number of dates must be equal."); CheckState (false, false); if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); var format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; format.EnsureNewLine = true; if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) format.International = true; if (format.International && !Engine.UTF8Enabled) throw new InvalidOperationException ("The UTF8 extension has not been enabled."); if (messages.Count == 0) return new UniqueId[0]; if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) { var ic = QueueMultiAppend (format, messages, flags, dates, null, cancellationToken, progress); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("APPEND", ic); var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid); if (append != null) return append.UidSet; return new UniqueId[0]; } // FIXME: use an aggregate progress reporter var uids = new List (); for (int i = 0; i < messages.Count; i++) { var uid = await AppendAsync (format, messages[i], flags[i], dates[i], null, doAsync, cancellationToken, progress).ConfigureAwait (false); if (uids != null && uid.HasValue) uids.Add (uid.Value); else uids = null; } if (uids == null) return new UniqueId[0]; return uids; } /// /// Append the specified messages to the folder. /// /// /// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages. /// /// The UIDs of the appended messages, if available; otherwise an empty array. /// The formatting options. /// The list of messages to append to the folder. /// The message flags to use for each of the messages. /// The received dates to use for each of the messages. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// -or- /// is null. /// -or- /// is null. /// /// /// One or more of the is null. /// -or- /// The number of messages, flags, and dates do not match. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override IList Append (FormatOptions options, IList messages, IList flags, IList dates, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, messages, flags, dates, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously append the specified messages to the folder. /// /// /// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages. /// /// The UIDs of the appended messages, if available; otherwise an empty array. /// The formatting options. /// The list of messages to append to the folder. /// The message flags to use for each of the messages. /// The received dates to use for each of the messages. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// -or- /// is null. /// -or- /// is null. /// /// /// One or more of the is null. /// -or- /// The number of messages, flags, and dates do not match. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task> AppendAsync (FormatOptions options, IList messages, IList flags, IList dates, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return AppendAsync (options, messages, flags, dates, true, cancellationToken, progress); } ImapCommand QueueReplace (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, CancellationToken cancellationToken, ITransferProgress progress) { var builder = new StringBuilder ($"UID REPLACE {uid} %F "); var list = new List (); list.Add (this); if ((flags & SettableFlags) != 0) builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags, 0)); if (date.HasValue) builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (date.Value)); //if (annotations != null && annotations.Count > 0) { // ImapUtils.FormatAnnotations (builder, annotations, list, false); // // if (builder[builder.Length - 1] != ' ') // builder.Append (' '); //} builder.Append ("%L\r\n"); list.Add (message); var command = builder.ToString (); var args = list.ToArray (); var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args); ic.Progress = progress; Engine.QueueCommand (ic); return ic; } async Task ReplaceAsync (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (options == null) throw new ArgumentNullException (nameof (options)); if (!uid.IsValid) throw new ArgumentException ("The uid is invalid.", nameof (uid)); if (message == null) throw new ArgumentNullException (nameof (message)); CheckState (true, true); if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { var appended = await AppendAsync (options, message, flags, date, annotations, doAsync, cancellationToken, progress).ConfigureAwait (false); await ModifyFlagsAsync (new[] { uid }, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false); if ((Engine.Capabilities & ImapCapabilities.UidPlus) != 0) await ExpungeAsync (new[] { uid }, doAsync, cancellationToken).ConfigureAwait (false); return appended; } if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); var format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; format.EnsureNewLine = true; if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) format.International = true; if (format.International && !Engine.UTF8Enabled) throw new InvalidOperationException ("The UTF8 extension has not been enabled."); var ic = QueueReplace (format, uid, message, flags, date, annotations, cancellationToken, progress); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("REPLACE", ic); var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid); if (append != null) return append.UidSet[0]; return null; } /// /// Replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The UID of the message to be replaced. /// The message. /// The message flags. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueId? Replace (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, uid, message, flags, null, null, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The UID of the message to be replaced. /// The message. /// The message flags. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task ReplaceAsync (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, uid, message, flags, null, null, true, cancellationToken, progress); } /// /// Replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The UID of the message to be replaced. /// The message. /// The message flags. /// The received date of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueId? Replace (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, uid, message, flags, date, null, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The UID of the message to be replaced. /// The message. /// The message flags. /// The received date of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is invalid. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task ReplaceAsync (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, uid, message, flags, date, null, true, cancellationToken, progress); } ImapCommand QueueReplace (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, CancellationToken cancellationToken, ITransferProgress progress) { var builder = new StringBuilder ($"REPLACE %d %F "); var list = new List (); list.Add (index + 1); list.Add (this); if ((flags & SettableFlags) != 0) builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags, 0)); if (date.HasValue) builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (date.Value)); //if (annotations != null && annotations.Count > 0) { // ImapUtils.FormatAnnotations (builder, annotations, list, false); // // if (builder[builder.Length - 1] != ' ') // builder.Append (' '); //} builder.Append ("%L\r\n"); list.Add (message); var command = builder.ToString (); var args = list.ToArray (); var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args); ic.Progress = progress; Engine.QueueCommand (ic); return ic; } async Task ReplaceAsync (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList annotations, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress) { if (options == null) throw new ArgumentNullException (nameof (options)); if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException (nameof (index)); if (message == null) throw new ArgumentNullException (nameof (message)); CheckState (true, true); if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { var uid = await AppendAsync (options, message, flags, date, annotations, doAsync, cancellationToken, progress).ConfigureAwait (false); await ModifyFlagsAsync (new[] { index }, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false); return uid; } if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); var format = options.Clone (); format.NewLineFormat = NewLineFormat.Dos; format.EnsureNewLine = true; if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) format.International = true; if (format.International && !Engine.UTF8Enabled) throw new InvalidOperationException ("The UTF8 extension has not been enabled."); var ic = QueueReplace (format, index, message, flags, date, annotations, cancellationToken, progress); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("REPLACE", ic); var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid); if (append != null) return append.UidSet[0]; return null; } /// /// Replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The index of the message to be replaced. /// The message. /// The message flags. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueId? Replace (FormatOptions options, int index, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, index, message, flags, null, null, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The index of the message to be replaced. /// The message. /// The message flags. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task ReplaceAsync (FormatOptions options, int index, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, index, message, flags, null, null, true, cancellationToken, progress); } /// /// Replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The index of the message to be replaced. /// The message. /// The message flags. /// The received date of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueId? Replace (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, index, message, flags, date, null, false, cancellationToken, progress).GetAwaiter ().GetResult (); } /// /// Asynchronously replace a message in the folder. /// /// /// Replaces the specified message in the folder and returns the UniqueId assigned to the new message. /// /// The UID of the new message, if available; otherwise, null. /// The formatting options. /// The index of the message to be replaced. /// The message. /// The message flags. /// The received date of the message. /// The cancellation token. /// The progress reporting mechanism. /// /// is null. /// -or- /// is null. /// /// /// is out of range. /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// Internationalized formatting was requested but has not been enabled. /// /// /// The does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// Internationalized formatting was requested but is not supported by the server. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task ReplaceAsync (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null) { return ReplaceAsync (options, index, message, flags, date, null, true, cancellationToken, progress); } async Task> GetIndexesAsync (IList uids, bool doAsync, CancellationToken cancellationToken) { var command = string.Format ("SEARCH UID {0}\r\n", UniqueIdSet.ToString (uids)); var ic = new ImapCommand (Engine, cancellationToken, this, command); var results = new SearchResults (SortOrder.Ascending); if ((Engine.Capabilities & ImapCapabilities.ESearch) != 0) ic.RegisterUntaggedHandler ("ESEARCH", ESearchMatchesAsync); ic.RegisterUntaggedHandler ("SEARCH", SearchMatchesAsync); ic.UserData = results; Engine.QueueCommand (ic); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("SEARCH", ic); var indexes = new int[results.UniqueIds.Count]; for (int i = 0; i < indexes.Length; i++) indexes[i] = (int) results.UniqueIds[i].Id - 1; return indexes; } async Task CopyToAsync (IList uids, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) { if (uids == null) throw new ArgumentNullException (nameof (uids)); if (destination == null) throw new ArgumentNullException (nameof (destination)); if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine) throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination)); CheckState (true, false); if (uids.Count == 0) return UniqueIdMap.Empty; if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { var indexes = await GetIndexesAsync (uids, doAsync, cancellationToken).ConfigureAwait (false); await CopyToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false); return UniqueIdMap.Empty; } UniqueIdSet dest = null; UniqueIdSet src = null; foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID COPY %s %F\r\n", uids, destination)) { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, destination); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("COPY", ic); var copy = (CopyUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.CopyUid); if (copy != null) { if (dest == null) { dest = copy.DestUidSet; src = copy.SrcUidSet; } else { dest.AddRange (copy.DestUidSet); src.AddRange (copy.SrcUidSet); } } } if (dest == null) return UniqueIdMap.Empty; return new UniqueIdMap (src, dest); } /// /// Copy the specified messages to the destination folder. /// /// /// Copies the specified messages to the destination folder. /// /// The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping. /// The UIDs of the messages to copy. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open. /// /// /// The IMAP server does not support the UIDPLUS extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueIdMap CopyTo (IList uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { return CopyToAsync (uids, destination, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously copy the specified messages to the destination folder. /// /// /// Copies the specified messages to the destination folder. /// /// The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping. /// The UIDs of the messages to copy. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open. /// /// /// The IMAP server does not support the UIDPLUS extension. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task CopyToAsync (IList uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { return CopyToAsync (uids, destination, true, cancellationToken); } async Task MoveToAsync (IList uids, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) { if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { var copied = await CopyToAsync (uids, destination, doAsync, cancellationToken).ConfigureAwait (false); await ModifyFlagsAsync (uids, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false); await ExpungeAsync (uids, doAsync, cancellationToken).ConfigureAwait (false); return copied; } if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { var indexes = await GetIndexesAsync (uids, doAsync, cancellationToken).ConfigureAwait (false); await MoveToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false); return UniqueIdMap.Empty; } if (uids == null) throw new ArgumentNullException (nameof (uids)); if (destination == null) throw new ArgumentNullException (nameof (destination)); if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine) throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination)); CheckState (true, true); if (uids.Count == 0) return UniqueIdMap.Empty; UniqueIdSet dest = null; UniqueIdSet src = null; foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID MOVE %s %F\r\n", uids, destination)) { await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, destination); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("MOVE", ic); var copy = (CopyUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.CopyUid); if (copy != null) { if (dest == null) { dest = copy.DestUidSet; src = copy.SrcUidSet; } else { dest.AddRange (copy.DestUidSet); src.AddRange (copy.SrcUidSet); } } } if (dest == null) return UniqueIdMap.Empty; return new UniqueIdMap (src, dest); } /// /// Move the specified messages to the destination folder. /// /// /// Moves the specified messages to the destination folder. /// If the IMAP server supports the MOVE extension (check the /// property for the flag), then this operation will be atomic. /// Otherwise, MailKit implements this by first copying the messages to the destination folder, then /// marking them for deletion in the originating folder, and finally expunging them (see /// for more information about how a /// subset of messages are expunged). Since the server could disconnect at any point between those 3 /// (or more) commands, it is advisable for clients to implement their own logic for moving messages when /// the IMAP server does not support the MOVE command in order to better handle spontanious server /// disconnects and other error conditions. /// /// The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping. /// The UIDs of the messages to move. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is empty. /// -or- /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override UniqueIdMap MoveTo (IList uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { return MoveToAsync (uids, destination, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously move the specified messages to the destination folder. /// /// /// Moves the specified messages to the destination folder. /// If the IMAP server supports the MOVE extension (check the /// property for the flag), then this operation will be atomic. /// Otherwise, MailKit implements this by first copying the messages to the destination folder, then /// marking them for deletion in the originating folder, and finally expunging them (see /// for more information about how a /// subset of messages are expunged). Since the server could disconnect at any point between those 3 /// (or more) commands, it is advisable for clients to implement their own logic for moving messages when /// the IMAP server does not support the MOVE command in order to better handle spontanious server /// disconnects and other error conditions. /// /// The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping. /// The UIDs of the messages to move. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// is empty. /// -or- /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task MoveToAsync (IList uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { return MoveToAsync (uids, destination, true, cancellationToken); } async Task CopyToAsync (IList indexes, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) { if (indexes == null) throw new ArgumentNullException (nameof (indexes)); if (destination == null) throw new ArgumentNullException (nameof (destination)); if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine) throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination)); CheckState (true, false); CheckAllowIndexes (); if (indexes.Count == 0) return; var set = ImapUtils.FormatIndexSet (indexes); var command = string.Format ("COPY {0} %F\r\n", set); var ic = Engine.QueueCommand (cancellationToken, this, command, destination); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, destination); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("COPY", ic); } /// /// Copy the specified messages to the destination folder. /// /// /// Copies the specified messages to the destination folder. /// /// The indexes of the messages to copy. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not currently open. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void CopyTo (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { CopyToAsync (indexes, destination, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously copy the specified messages to the destination folder. /// /// /// Copies the specified messages to the destination folder. /// /// An awaitable task. /// The indexes of the messages to copy. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not currently open. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task CopyToAsync (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { return CopyToAsync (indexes, destination, true, cancellationToken); } async Task MoveToAsync (IList indexes, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) { if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { await CopyToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false); await ModifyFlagsAsync (indexes, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false); return; } if (indexes == null) throw new ArgumentNullException (nameof (indexes)); if (destination == null) throw new ArgumentNullException (nameof (destination)); if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine) throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination)); CheckState (true, true); CheckAllowIndexes (); if (indexes.Count == 0) return; var set = ImapUtils.FormatIndexSet (indexes); var command = string.Format ("MOVE {0} %F\r\n", set); var ic = Engine.QueueCommand (cancellationToken, this, command, destination); await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); ProcessResponseCodes (ic, destination); if (ic.Response != ImapCommandResponse.Ok) throw ImapCommandException.Create ("MOVE", ic); } /// /// Move the specified messages to the destination folder. /// /// /// If the IMAP server supports the MOVE command, then the MOVE command will be used. Otherwise, /// the messages will first be copied to the destination folder and then marked as \Deleted in the /// originating folder. Since the server could disconnect at any point between those 2 operations, it /// may be advisable to implement your own logic for moving messages in this case in order to better /// handle spontanious server disconnects and other error conditions. /// /// The indexes of the messages to move. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override void MoveTo (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { MoveToAsync (indexes, destination, false, cancellationToken).GetAwaiter ().GetResult (); } /// /// Asynchronously move the specified messages to the destination folder. /// /// /// If the IMAP server supports the MOVE command, then the MOVE command will be used. Otherwise, /// the messages will first be copied to the destination folder and then marked as \Deleted in the /// originating folder. Since the server could disconnect at any point between those 2 operations, it /// may be advisable to implement your own logic for moving messages in this case in order to better /// handle spontanious server disconnects and other error conditions. /// /// An awaitable task. /// The indexes of the messages to move. /// The destination folder. /// The cancellation token. /// /// is null. /// -or- /// is null. /// /// /// One or more of the is invalid. /// -or- /// The destination folder does not belong to the . /// /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// does not exist. /// /// /// The is not currently open in read-write mode. /// /// /// The operation was canceled via the cancellation token. /// /// /// An I/O error occurred. /// /// /// The server's response contained unexpected tokens. /// /// /// The server replied with a NO or BAD response. /// public override Task MoveToAsync (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken)) { return MoveToAsync (indexes, destination, true, cancellationToken); } #region IEnumerable implementation /// /// Get an enumerator for the messages in the folder. /// /// /// Gets an enumerator for the messages in the folder. /// /// The enumerator. /// /// The has been disposed. /// /// /// The is not connected. /// /// /// The is not authenticated. /// /// /// The is not currently open. /// public override IEnumerator GetEnumerator () { CheckState (true, false); for (int i = 0; i < Count; i++) yield return GetMessage (i, CancellationToken.None); yield break; } #endregion #region Untagged response handlers called by ImapEngine internal void OnExists (int count) { if (Count == count) return; Count = count; OnCountChanged (); } internal void OnExpunge (int index) { Count--; OnMessageExpunged (new MessageEventArgs (index)); OnCountChanged (); } internal async Task OnFetchAsync (ImapEngine engine, int index, bool doAsync, CancellationToken cancellationToken) { var message = new MessageSummary (this, index); UniqueId? uid = null; await FetchSummaryItemsAsync (engine, message, doAsync, cancellationToken).ConfigureAwait (false); if ((message.Fields & MessageSummaryItems.UniqueId) != 0) uid = message.UniqueId; if ((message.Fields & MessageSummaryItems.Flags) != 0) { var args = new MessageFlagsChangedEventArgs (index, message.Flags.Value, message.Keywords); args.ModSeq = message.ModSeq; args.UniqueId = uid; OnMessageFlagsChanged (args); } if ((message.Fields & MessageSummaryItems.GMailLabels) != 0) { var args = new MessageLabelsChangedEventArgs (index, message.GMailLabels); args.ModSeq = message.ModSeq; args.UniqueId = uid; OnMessageLabelsChanged (args); } if ((message.Fields & MessageSummaryItems.Annotations) != 0) { var args = new AnnotationsChangedEventArgs (index, message.Annotations); args.ModSeq = message.ModSeq; args.UniqueId = uid; OnAnnotationsChanged (args); } if ((message.Fields & MessageSummaryItems.ModSeq) != 0) { var args = new ModSeqChangedEventArgs (index, message.ModSeq.Value); args.UniqueId = uid; OnModSeqChanged (args); } if (message.Fields != MessageSummaryItems.None) OnMessageSummaryFetched (message); } internal void OnRecent (int count) { if (Recent == count) return; Recent = count; OnRecentChanged (); } internal async Task OnVanishedAsync (ImapEngine engine, bool doAsync, CancellationToken cancellationToken) { var token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); UniqueIdSet vanished; bool earlier = false; if (token.Type == ImapTokenType.OpenParen) { do { token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); if (token.Type == ImapTokenType.CloseParen) break; ImapEngine.AssertToken (token, ImapTokenType.Atom, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "VANISHED", token); var atom = (string) token.Value; if (atom == "EARLIER") earlier = true; } while (true); token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false); } vanished = ImapEngine.ParseUidSet (token, UidValidity, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "VANISHED", token); OnMessagesVanished (new MessagesVanishedEventArgs (vanished, earlier)); if (!earlier) { Count -= vanished.Count; OnCountChanged (); } } internal void UpdateAttributes (FolderAttributes attrs) { var unsubscribed = false; var subscribed = false; if ((attrs & FolderAttributes.Subscribed) == 0) unsubscribed = (Attributes & FolderAttributes.Subscribed) != 0; else subscribed = (Attributes & FolderAttributes.Subscribed) == 0; var deleted = ((attrs & FolderAttributes.NonExistent) != 0) && (Attributes & FolderAttributes.NonExistent) == 0; Attributes = attrs; if (unsubscribed) OnUnsubscribed (); if (subscribed) OnSubscribed (); if (deleted) OnDeleted (); } internal void UpdateAcceptedFlags (MessageFlags flags) { AcceptedFlags = flags; } internal void UpdatePermanentFlags (MessageFlags flags) { PermanentFlags = flags; } internal void UpdateIsNamespace (bool value) { IsNamespace = value; } internal void UpdateUnread (int count) { if (Unread == count) return; Unread = count; OnUnreadChanged (); } internal void UpdateUidNext (UniqueId uid) { if (UidNext.HasValue && UidNext.Value == uid) return; UidNext = uid; OnUidNextChanged (); } internal void UpdateAppendLimit (uint? limit) { AppendLimit = limit; } internal void UpdateSize (ulong? size) { if (Size == size) return; Size = size; OnSizeChanged (); } internal void UpdateId (string id) { if (Id == id) return; Id = id; OnIdChanged (); } internal void UpdateHighestModSeq (ulong modseq) { if (HighestModSeq == modseq) return; HighestModSeq = modseq; OnHighestModSeqChanged (); } internal void UpdateUidValidity (uint validity) { if (UidValidity == validity) return; UidValidity = validity; OnUidValidityChanged (); } internal void OnRenamed (ImapFolderConstructorArgs args) { var oldFullName = FullName; InitializeProperties (args); OnRenamed (oldFullName, FullName); } #endregion #endregion } }