Skip to content
Browse files

initial commit

  • Loading branch information...
0 parents commit ea961ceba2b99df6699f715e087a8c366cc17bac @smiley22 committed
Showing with 2,631 additions and 0 deletions.
  1. +22 −0 .gitattributes
  2. +163 −0 .gitignore
  3. +23 −0 AuthMethod.cs
  4. +219 −0 Examples.xml
  5. +115 −0 Exceptions.cs
  6. +53 −0 IdleEvents.cs
  7. +900 −0 ImapClient.cs
  8. +22 −0 License.md
  9. +25 −0 MIMEPart.cs
  10. +38 −0 MailboxStatus.cs
  11. +336 −0 MessageReader.cs
  12. +36 −0 Properties/AssemblyInfo.cs
  13. +47 −0 Readme.md
  14. +71 −0 S22.Imap.csproj
  15. +30 −0 S22.Imap.sln
  16. +378 −0 SearchCondition.cs
  17. +153 −0 Util.cs
22 .gitattributes
@@ -0,0 +1,22 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs diff=csharp
+*.sln merge=union
+*.csproj merge=union
+*.vbproj merge=union
+*.fsproj merge=union
+*.dbproj merge=union
+
+# Standard to msysgit
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
163 .gitignore
@@ -0,0 +1,163 @@
+#################
+## Eclipse
+#################
+
+*.pydevproject
+.project
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+
+#################
+## Visual Studio
+#################
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Rr]elease/
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.vspscc
+.builds
+*.dotCover
+
+## TODO: If you have NuGet Package Restore enabled, uncomment this
+#packages/
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+
+# Visual Studio profiler
+*.psess
+*.vsp
+
+# ReSharper is a .NET coding add-in
+_ReSharper*
+
+# Installshield output folder
+[Ee]xpress
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish
+
+# Others
+[Bb]in
+[Oo]bj
+sql
+TestResults
+*.Cache
+ClientBin
+stylecop.*
+~$*
+*.dbmdl
+Generated_Code #added for RIA/Silverlight projects
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+
+
+
+############
+## Windows
+############
+
+# Windows image file caches
+Thumbs.db
+
+# Folder config file
+Desktop.ini
+
+
+#############
+## Python
+#############
+
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg
+
+# Mac crap
+.DS_Store
23 AuthMethod.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace S22.Imap {
+ /// <summary>
+ /// Defines supported means of authenticating with the IMAP server.
+ /// </summary>
+ public enum AuthMethod {
+ /// <summary>
+ /// Login using plaintext password authentication. This is
+ /// the default supported by most servers.
+ /// </summary>
+ Login,
+ /// <summary>
+ /// Login using the CRAM-MD5 authentication mechanism.
+ /// </summary>
+ CRAMMD5,
+ /// <summary>
+ /// Login using the OAuth authentication mechanism over
+ /// the Simple Authentication and Security Layer (Sasl).
+ /// </summary>
+ SaslOAuth
+ }
+}
219 Examples.xml
@@ -0,0 +1,219 @@
+<S22>
+ <Imap>
+ <ImapClient name="ctor-1">
+ <example>
+ This example shows how to establish a connection with an IMAP server
+ and print out the IMAP options, which the server supports.
+ <code>
+ /* Connect to Gmail's IMAP server on port 993 using SSL */
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, true);
+
+ /* Print out the server's capabilities */
+ foreach(string s in Client.Capabilities())
+ Console.WriteLine(s);
+
+ Client.Dispose();
+ </code>
+ </example>
+ </ImapClient>
+
+ <ImapClient name="ctor-2">
+ <example>
+ This example demonstrates how to connect and login to an IMAP server.
+ <code>
+ /* Connect to Gmail's IMAP server on port 993 using SSL */
+ try {
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_Username",
+ "My_Password", true, AuthMethod.Login);
+
+ /* Check if the server supports IMAP IDLE */
+ if(Client.Supports("IDLE"))
+ Console.WriteLine("This server supports the IMAP4 IDLE specification");
+ else
+ Console.WriteLine("This server does not support IMAP IDLE");
+
+ /* release resources */
+ Client.Dispose();
+ }
+ catch(InvalidCredentialsException) {
+ Console.WriteLine("The server rejected the supplied credentials");
+ }
+ </code>
+ </example>
+ </ImapClient>
+
+ <ImapClient name="Login">
+ <example>
+ This example demonstrates how to authenticate with an IMAP server once a connection
+ has been established. Notice that you can also connect and login in one step
+ using one of the overloaded constructors.
+ <code>
+ /* Connect to Gmail's IMAP server on port 993 using SSL */
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, true);
+
+ try {
+ Client.Login("My_Username", "My_Password", AuthMethod.Login);
+ }
+ catch(InvalidCredentialsException) {
+ Console.WriteLine("The server rejected the supplied credentials");
+ }
+
+ Client.Dispose();
+ </code>
+ </example>
+ </ImapClient>
+
+ <ImapClient name="Search">
+ <example>
+ This example demonstrates how to use the search method to get a list of all
+ unread messages in the mailbox.
+ <code>
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_Username",
+ "My_Password", true, AuthMethod.Login);
+
+ /* get a list of unique identifiers (UIDs) of all unread messages in the mailbox */
+ uint[] uids = Client.Search( SearchCondition.Unseen() );
+
+ /* fetch the messages and print out their subject lines */
+ foreach(uint uid in uids) {
+ MailMessage message = Client.GetMessage(uid);
+
+ Console.WriteLine(message.Subject);
+ }
+
+ /* free up any resources associated with this instance */
+ Client.Dispose();
+ </code>
+ </example>
+ <example>
+ This example demonstrates how to perform a search using multiple search criteria
+ <code>
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_Username",
+ "My_Password", true, AuthMethod.Login);
+
+ /* get a list of unique identifiers (UIDs) of all messages sent before the 01.04.2012
+ and that are larger than 1 Kilobyte */
+ uint[] uids = Client.Search( SearchCondition.SentBefore(new DateTime(2012, 4, 1))
+ .And( SearchCondition.Larger(1024) ));
+
+ Console.WriteLine("Found " + uids.Length + " messages");
+
+ /* free up any resources associated with this instance */
+ Client.Dispose();
+ </code>
+ </example>
+ </ImapClient>
+ <ImapClient name="GetMessage">
+ <example>
+ <code>
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_UsernamMe",
+ "My_Password", true, AuthMethod.Login);
+
+ /* find all messages in the mailbox that were sent from "John.Doe@gmail.com" */
+ uint uids = Client.Search( SearchCondition.From("John.Doe@gmail.com") );
+
+ /* fetch the first message and print it's subject and body */
+ if(uids.Length > 0) {
+ MailMessage msg = Client.GetMessage(uids[0]);
+
+ Console.WriteLine("Subject: " + msg.Subject);
+ Console.WriteLine("Body: " + msg.Body);
+ }
+
+ Client.Dispose();
+ </code>
+ </example>
+ </ImapClient>
+
+ <ImapClient name="GetMessages">
+ <example>
+ <code>
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_UsernamMe",
+ "My_Password", true, AuthMethod.Login);
+
+ /* find all messages that have been sent since June the 1st */
+ uint uids = Client.Search( SearchCondition.SentSince( new DateTime(2012, 6, 1) ) );
+
+ /* fetch the messages and print out their subject lines */
+ MailMessage[] messages = Client.GetMessages( uids );
+
+ foreach(MailMessage m in messages)
+ Console.WriteLine("Subject: " + m.Subject);
+
+ Client.Dispose();
+ </code>
+ </example>
+ </ImapClient>
+
+ <ImapClient name="GetStatus">
+ <example>
+ <code>
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_UsernamMe",
+ "My_Password", true, AuthMethod.Login);
+
+ MailboxStatus status = Client.GetStatus();
+
+ Console.WriteLine("Number of messages in the mailbox: " + status.Messages);
+ Console.WriteLine("Number of unread messages in the mailbox: " + status.Unread);
+
+ Client.Dispose();
+ </code>
+ </example>
+ </ImapClient>
+
+ <ImapClient name="NewMessage">
+ <example>
+ This example demonstrates how to receive IMAP IDLE notifications.
+ <code>
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_UsernamMe",
+ "My_Password", true, AuthMethod.Login);
+
+ /* make sure our server actually supports IMAP IDLE */
+ if(!Client.Supports("IDLE"))
+ throw new Exception("This server does not support IMAP IDLE");
+
+ /* Our event handler will be called whenever a new message is received
+ by the server. */
+ Client.NewMessage += new EventHandler&lt;IdleMessageEventArgs&gt;(OnNewMessage);
+
+ Client.Dispose();
+
+ /* ........ */
+
+ void OnNewMessage(object sender, IdleMessageEventArgs e) {
+ Console.WriteLine("Received a new message!");
+ Console.WriteLine("Total number of messages in the mailbox: " +
+ e.MessageCount);
+ }
+ </code>
+ </example>
+ </ImapClient>
+
+ <ImapClient name="MessageDeleted">
+ <example>
+ This example demonstrates how to receive IMAP IDLE notifications.
+ <code>
+ ImapClient Client = new ImapClient("imap.gmail.com", 993, "My_UsernamMe",
+ "My_Password", true, AuthMethod.Login);
+
+ /* make sure our server actually supports IMAP IDLE */
+ if(!Client.Supports("IDLE"))
+ throw new Exception("This server does not support IMAP IDLE");
+
+ /* Our event handler will be called whenever a message is deleted on the server. */
+ Client.MessageDeleted += new EventHandler&lt;IdleMessageEventArgs&gt;(OnMessageDeleted);
+
+ Client.Dispose();
+
+ /* ........ */
+
+ void OnMessageDeleted(object sender, IdleMessageEventArgs e) {
+ Console.WriteLine("A mail message was deleted on the server!");
+ Console.WriteLine("Total number of mail messages in the mailbox: " +
+ e.MessageCount);
+ }
+ </code>
+ </example>
+ </ImapClient>
+ </Imap>
+</S22>
115 Exceptions.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace S22.Imap {
+ /// <summary>
+ /// The exception is thrown when an unexpected response is received from the server.
+ /// </summary>
+ [Serializable()]
+ public class BadServerResponseException : Exception {
+ /// <summary>
+ /// Initializes a new instance of the BadServerResponseException class
+ /// </summary>
+ public BadServerResponseException() : base() { }
+ /// <summary>
+ /// Initializes a new instance of the BadServerResponseException class with its message
+ /// string set to <paramref name="message"/>.
+ /// </summary>
+ /// <param name="message">A description of the error. The content of message is intended
+ /// to be understood by humans.</param>
+ public BadServerResponseException(string message) : base(message) { }
+ /// <summary>
+ /// Initializes a new instance of the BadServerResponseException class with its message
+ /// string set to <paramref name="message"/> and a reference to the inner exception that
+ /// is the cause of this exception.
+ /// </summary>
+ /// <param name="message">A description of the error. The content of message is intended
+ /// to be understood by humans.</param>
+ /// <param name="inner">The exception that is the cause of the current exception.</param>
+ public BadServerResponseException(string message, Exception inner) : base(message, inner) { }
+ /// <summary>
+ /// Initializes a new instance of the BadServerResponseException class with the specified
+ /// serialization and context information.
+ /// </summary>
+ /// <param name="info">An object that holds the serialized object data about the exception
+ /// being thrown. </param>
+ /// <param name="context">An object that contains contextual information about the source
+ /// or destination. </param>
+ protected BadServerResponseException(SerializationInfo info, StreamingContext context) { }
+ }
+
+ /// <summary>
+ /// This exception is thrown when the supplied credentials in a login attempt were rejected
+ /// by the server.
+ /// </summary>
+ [Serializable()]
+ public class InvalidCredentialsException : Exception {
+ /// <summary>
+ /// Initializes a new instance of the InvalidCredentialsException class
+ /// </summary>
+ public InvalidCredentialsException() : base() { }
+ /// <summary>
+ /// Initializes a new instance of the InvalidCredentialsException class with its message
+ /// string set to <paramref name="message"/>.
+ /// </summary>
+ /// <param name="message">A description of the error. The content of message is intended
+ /// to be understood by humans.</param>
+ public InvalidCredentialsException(string message) : base(message) { }
+ /// <summary>
+ /// Initializes a new instance of the InvalidCredentialsException class with its message
+ /// string set to <paramref name="message"/> and a reference to the inner exception that
+ /// is the cause of this exception.
+ /// </summary>
+ /// <param name="message">A description of the error. The content of message is intended
+ /// to be understood by humans.</param>
+ /// <param name="inner">The exception that is the cause of the current exception.</param>
+ public InvalidCredentialsException(string message, Exception inner) : base(message, inner) { }
+ /// <summary>
+ /// Initializes a new instance of the InvalidCredentialsException class with the specified
+ /// serialization and context information.
+ /// </summary>
+ /// <param name="info">An object that holds the serialized object data about the exception
+ /// being thrown. </param>
+ /// <param name="context">An object that contains contextual information about the source
+ /// or destination. </param>
+ protected InvalidCredentialsException(SerializationInfo info, StreamingContext context) { }
+ }
+
+ /// <summary>
+ /// This exception is thrown when a client has not authenticated with the server and
+ /// attempts to call a method which can only be called in an authenticated context.
+ /// </summary>
+ [Serializable()]
+ public class NotAuthenticatedException : Exception {
+ /// <summary>
+ /// Initializes a new instance of the NotAuthenticatedException class
+ /// </summary>
+ public NotAuthenticatedException() : base() { }
+ /// <summary>
+ /// Initializes a new instance of the NotAuthenticatedException class with its message
+ /// string set to <paramref name="message"/>.
+ /// </summary>
+ /// <param name="message">A description of the error. The content of message is intended
+ /// to be understood by humans.</param>
+ public NotAuthenticatedException(string message) : base(message) { }
+ /// <summary>
+ /// Initializes a new instance of the NotAuthenticatedException class with its message
+ /// string set to <paramref name="message"/> and a reference to the inner exception that
+ /// is the cause of this exception.
+ /// </summary>
+ /// <param name="message">A description of the error. The content of message is intended
+ /// to be understood by humans.</param>
+ /// <param name="inner">The exception that is the cause of the current exception.</param>
+ public NotAuthenticatedException(string message, Exception inner) : base(message, inner) { }
+ /// <summary>
+ /// Initializes a new instance of the NotAuthenticatedException class with the specified
+ /// serialization and context information.
+ /// </summary>
+ /// <param name="info">An object that holds the serialized object data about the exception
+ /// being thrown. </param>
+ /// <param name="context">An object that contains contextual information about the source
+ /// or destination. </param>
+ protected NotAuthenticatedException(SerializationInfo info, StreamingContext context) { }
+ }
+
+}
53 IdleEvents.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace S22.Imap {
+ /// <summary>
+ /// Provides data for IMAP idle notification events, such as the NewMessage and
+ /// MessageDelete events.
+ /// </summary>
+ public class IdleMessageEventArgs : EventArgs {
+ /// <summary>
+ /// Initializes a new instance of the IdleMessageEventArgs class and sets the
+ /// MessageCount attribute to the value of the <paramref name="MessageCount"/>
+ /// parameter.
+ /// </summary>
+ /// <param name="MessageCount">The number of messages in the selected
+ /// mailbox.</param>
+ /// <param name="MessageUID"> The unique identifier (UID) of the newest
+ /// message in the mailbox.</param>
+ /// <param name="Client">The instance of the ImapClient class that raised
+ /// the event.</param>
+ internal IdleMessageEventArgs(uint MessageCount, uint MessageUID,
+ ImapClient Client) {
+ this.MessageCount = MessageCount;
+ this.MessageUID = MessageUID;
+ this.Client = Client;
+ }
+
+ /// <summary>
+ /// The total number of messages in the selected mailbox.
+ /// </summary>
+ public uint MessageCount {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// The unique identifier (UID) of the newest message in the mailbox.
+ /// </summary>
+ /// <remarks>The UID can be passed to the GetMessage method in order to retrieve
+ /// the mail message from the server.</remarks>
+ public uint MessageUID {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// The instance of the ImapClient class that raised the event.
+ /// </summary>
+ public ImapClient Client {
+ get;
+ private set;
+ }
+ }
+}
900 ImapClient.cs
@@ -0,0 +1,900 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Mail;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+namespace S22.Imap {
+ /// <summary>
+ /// Allows applications to communicate with a mail server by using the
+ /// Internet Message Access Protocol (IMAP).
+ /// </summary>
+ public class ImapClient : IDisposable {
+ private Stream stream;
+ private TcpClient client;
+ private readonly object readLock = new object();
+ private readonly object writeLock = new object();
+ private string[] capabilities;
+ private int tag = 0;
+ private string selectedMailbox;
+ private string defaultMailbox = "INBOX";
+ private event EventHandler<IdleMessageEventArgs> newMessageEvent;
+ private event EventHandler<IdleMessageEventArgs> messageDeleteEvent;
+ private bool hasEvents {
+ get {
+ return newMessageEvent != null || messageDeleteEvent != null;
+ }
+ }
+ private bool idling;
+ private Thread idleThread;
+
+ /// <summary>
+ /// The default mailbox to operate on, when no specific mailbox name was indicated
+ /// to methods operating on mailboxes. This property is initially set to "INBOX".
+ /// </summary>
+ /// <exception cref="ArgumentNullException">The value specified for a set operation is
+ /// null.</exception>
+ /// <exception cref="ArgumentException">The value specified for a set operation is equal
+ /// to String.Empty ("").</exception>
+ /// <remarks>This property is initialized to "INBOX"</remarks>
+ public string DefaultMailbox {
+ get {
+ return defaultMailbox;
+ }
+ set {
+ if (value == null)
+ throw new ArgumentNullException();
+ if (value == String.Empty)
+ throw new ArgumentException();
+ defaultMailbox = value;
+ }
+ }
+
+ /// <summary>
+ /// Indicates whether the client is authenticated with the server
+ /// </summary>
+ public bool Authed {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// This event is raised when a new mail message is received by the server.
+ /// </summary>
+ /// <remarks>To probe a server for IMAP IDLE support, the <see cref="Supports"/>
+ /// method can be used, specifying "IDLE" for the capability parameter.
+ ///
+ /// Notice that the event handler will be executed on a threadpool thread.
+ /// </remarks>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="NewMessage"]/*'/>
+ public event EventHandler<IdleMessageEventArgs> NewMessage {
+ add {
+ newMessageEvent += value;
+ StartIdling();
+ }
+ remove {
+ newMessageEvent -= value;
+ if (!hasEvents)
+ StopIdling();
+ }
+ }
+
+ /// <summary>
+ /// This event is raised when a message is deleted on the server.
+ /// </summary>
+ /// <remarks>To probe a server for IMAP IDLE support, the <see cref="Supports"/>
+ /// method can be used, specifying "IDLE" for the capability parameter.
+ ///
+ /// Notice that the event handler will be executed on a threadpool thread.
+ /// </remarks>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="MessageDeleted"]/*'/>
+ public event EventHandler<IdleMessageEventArgs> MessageDeleted {
+ add {
+ messageDeleteEvent += value;
+ StartIdling();
+ }
+ remove {
+ messageDeleteEvent -= value;
+ if (!hasEvents)
+ StopIdling();
+ }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the ImapClient class and connects to the specified port
+ /// on the specified host, optionally using the Secure Socket Layer (SSL) security protocol.
+ /// </summary>
+ /// <param name="hostname">The DNS name of the server to which you intend to connect.</param>
+ /// <param name="port">The port number of the server to which you intend to connect.</param>
+ /// <param name="ssl">Set to true to use the Secure Socket Layer (SSL) security protocol.</param>
+ /// <param name="validate">Delegate used for verifying the remote Secure Sockets
+ /// Layer (SSL) certificate which is used for authentication. Set this to null if not needed</param>
+ /// <exception cref="ArgumentOutOfRangeException">The port parameter is not between MinPort
+ /// and MaxPort.</exception>
+ /// <exception cref="ArgumentNullException">The hostname parameter is null.</exception>
+ /// <exception cref="SocketException">An error occurred while accessing the socket used for
+ /// establishing the connection to the IMAP server. Use the ErrorCode property to obtain the
+ /// specific error code</exception>
+ /// <exception cref="System.Security.Authentication.AuthenticationException">An authentication
+ /// error occured while trying to establish a secure connection.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if an unexpected response is received
+ /// from the server upon connecting.</exception>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="ctor-1"]/*'/>
+ public ImapClient(string hostname, int port = 143, bool ssl = false,
+ RemoteCertificateValidationCallback validate = null) {
+ Connect(hostname, port, ssl, validate);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the ImapClient class and connects to the specified port on
+ /// the specified host, optionally using the Secure Socket Layer (SSL) security protocol and
+ /// attempts to authenticate with the server using the specified authentication method and
+ /// credentials.
+ /// </summary>
+ /// <param name="hostname">The DNS name of the server to which you intend to connect.</param>
+ /// <param name="port">The port number of the server to which you intend to connect.</param>
+ /// <param name="username">The username with which to login in to the IMAP server.</param>
+ /// <param name="password">The password with which to log in to the IMAP server.</param>
+ /// <param name="method">The requested method of authentication. Can be one of the values
+ /// of the AuthMethod enumeration.</param>
+ /// <param name="ssl">Set to true to use the Secure Socket Layer (SSL) security protocol.</param>
+ /// <param name="validate">Delegate used for verifying the remote Secure Sockets Layer
+ /// (SSL) certificate which is used for authentication. Set this to null if not needed</param>
+ /// <exception cref="ArgumentOutOfRangeException">The port parameter is not between MinPort
+ /// and MaxPort.</exception>
+ /// <exception cref="ArgumentNullException">The hostname parameter is null.</exception>
+ /// <exception cref="SocketException">An error occurred while accessing the socket used for
+ /// establishing the connection to the IMAP server. Use the ErrorCode property to obtain the
+ /// specific error code</exception>
+ /// <exception cref="System.Security.Authentication.AuthenticationException">An authentication
+ /// error occured while trying to establish a secure connection.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if an unexpected response is received
+ /// from the server upon connecting.</exception>
+ /// <exception cref="InvalidCredentialsException">Thrown if authentication using the
+ /// supplied credentials failed.</exception>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="ctor-2"]/*'/>
+ public ImapClient(string hostname, int port, string username, string password, AuthMethod method =
+ AuthMethod.Login, bool ssl = false, RemoteCertificateValidationCallback validate = null) {
+ Connect(hostname, port, ssl, validate);
+ Login(username, password, method);
+ }
+
+ /// <summary>
+ /// Connects to the specified port on the specified host, optionally using the Secure Socket Layer
+ /// (SSL) security protocol.
+ /// </summary>
+ /// <param name="hostname">The DNS name of the server to which you intend to connect.</param>
+ /// <param name="port">The port number of the server to which you intend to connect.</param>
+ /// <param name="ssl">Set to true to use the Secure Socket Layer (SSL) security protocol.</param>
+ /// <param name="validate">Delegate used for verifying the remote Secure Sockets
+ /// Layer (SSL) certificate which is used for authentication. Set this to null if not needed</param>
+ /// <exception cref="ArgumentOutOfRangeException">The port parameter is not between MinPort
+ /// and MaxPort.</exception>
+ /// <exception cref="ArgumentNullException">The hostname parameter is null.</exception>
+ /// <exception cref="SocketException">An error occurred while accessing the socket used for
+ /// establishing the connection to the IMAP server. Use the ErrorCode property to obtain the
+ /// specific error code.</exception>
+ /// <exception cref="System.Security.Authentication.AuthenticationException">An authentication
+ /// error occured while trying to establish a secure connection.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if an unexpected response is received
+ /// from the server upon connecting.</exception>
+ private void Connect(string hostname, int port, bool ssl, RemoteCertificateValidationCallback validate) {
+ client = new TcpClient(hostname, port);
+ stream = client.GetStream();
+ if (ssl) {
+ SslStream sslStream = new SslStream(stream, false, validate ??
+ ((sender, cert, chain, err) => true));
+ sslStream.AuthenticateAsClient(hostname);
+ stream = sslStream;
+ }
+ /* Server issues untagged OK greeting upon connect */
+ string greeting = GetResponse();
+ if (!IsResponseOK(greeting))
+ throw new BadServerResponseException(greeting);
+ }
+
+ /// <summary>
+ /// Determines whether the received response is a valid IMAP OK response.
+ /// </summary>
+ /// <param name="response">A response string received from the server</param>
+ /// <param name="tag">A tag if the response is associated with a command</param>
+ /// <returns>True if the response is a valid IMAP OK response, otherwise false
+ /// is returned.</returns>
+ private bool IsResponseOK(string response, string tag = null) {
+ if (tag != null)
+ return response.StartsWith(tag + "OK");
+ string v = response.Substring(response.IndexOf(' ')).Trim();
+ return v.StartsWith("OK");
+ }
+
+ /// <summary>
+ /// Attempts to establish an authenticated session with the server using the specified
+ /// credentials.
+ /// </summary>
+ /// <param name="username">The username with which to login in to the IMAP server.</param>
+ /// <param name="password">The password with which to log in to the IMAP server.</param>
+ /// <param name="method">The requested method of authentication. Can be one of the values
+ /// of the AuthMethod enumeration.</param>
+ /// <exception cref="InvalidCredentialsException">Thrown if authentication using the
+ /// supplied credentials failed.</exception>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="Login"]/*'/>
+ public void Login(string username, string password, AuthMethod method) {
+ string tag = GetTag();
+ string response = null;
+ switch (method) {
+ case AuthMethod.Login:
+ response = SendCommandGetResponse(tag + "LOGIN " + username.QuoteString() + " " +
+ password.QuoteString());
+ break;
+ case AuthMethod.CRAMMD5:
+ response = SendCommandGetResponse(tag + "AUTHENTICATE CRAM-MD5");
+ /* retrieve server key */
+ string key = Encoding.Default.GetString(
+ Convert.FromBase64String(response.Replace("+ ", "")));
+ /* compute the hash */
+ using (var kMd5 = new HMACMD5(Encoding.ASCII.GetBytes(password))) {
+ byte[] hash1 = kMd5.ComputeHash(Encoding.ASCII.GetBytes(key));
+ key = BitConverter.ToString(hash1).ToLower().Replace("-", "");
+ string command = Convert.ToBase64String(
+ Encoding.ASCII.GetBytes(username + " " + key));
+ response = SendCommandGetResponse(command);
+ }
+ break;
+ case AuthMethod.SaslOAuth:
+ response = SendCommandGetResponse(tag + "AUTHENTICATE XOAUTH " + password);
+ break;
+ }
+ /* Server may include a CAPABILITY response */
+ if (response.StartsWith("* CAPABILITY")) {
+ capabilities = response.Substring(13).Trim().Split(' ');
+ response = GetResponse();
+ }
+
+ if (!IsResponseOK(response, tag))
+ throw new InvalidCredentialsException(response);
+ Authed = true;
+ }
+
+ /// <summary>
+ /// Logs an authenticated client out of the server. After the logout sequence has
+ /// been completed, the server closes the connection with the client.
+ /// </summary>
+ /// <exception cref="BadServerResponseException">Thrown if an unexpected response is
+ /// received from the server during the logout sequence</exception>
+ /// <remarks>Calling Logout in a non-authenticated state has no effect</remarks>
+ public void Logout() {
+ if (!Authed)
+ return;
+ StopIdling();
+ string tag = GetTag();
+ string bye = SendCommandGetResponse(tag + "LOGOUT");
+ if (!bye.StartsWith("* BYE"))
+ throw new BadServerResponseException(bye);
+ string response = GetResponse();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ Authed = false;
+ }
+
+ /// <summary>
+ /// Generates a unique identifier to prefix a command with, as is
+ /// required by the IMAP protocol.
+ /// </summary>
+ /// <returns>A unique identifier string</returns>
+ private string GetTag() {
+ Interlocked.Increment(ref tag);
+ return string.Format("xm{0:000} ", tag);
+ }
+
+ /// <summary>
+ /// Sends a command string to the server. This method blocks until the command has
+ /// been transmitted.
+ /// </summary>
+ /// <param name="command">Command string to be sent to the server. The command string is
+ /// suffixed by CRLF (as is required by the IMAP protocol) prior to sending.</param>
+ private void SendCommand(string command) {
+ byte[] bytes = Encoding.ASCII.GetBytes(command + "\r\n");
+ lock (writeLock) {
+ stream.Write(bytes, 0, bytes.Length);
+ }
+ }
+
+ /// <summary>
+ /// Sends a command string to the server and subsequently waits for a response, which is
+ /// then returned to the caller. This method blocks until the server response has been
+ /// received.
+ /// </summary>
+ /// <param name="command">Command string to be sent to the server. The command string is
+ /// suffixed by CRLF (as is required by the IMAP protocol) prior to sending.</param>
+ /// <returns>The response received by the server.</returns>
+ private string SendCommandGetResponse(string command) {
+ lock (readLock) {
+ lock (writeLock) {
+ SendCommand(command);
+ }
+ return GetResponse();
+ }
+ }
+
+ /// <summary>
+ /// Waits for a response from the server. This method blocks
+ /// until a response has been received.
+ /// </summary>
+ /// <returns>A response string from the server</returns>
+ private string GetResponse() {
+ const int Newline = 10, CarriageReturn = 13;
+ using (var mem = new MemoryStream()) {
+ lock (readLock) {
+ while (true) {
+ byte b = (byte)stream.ReadByte();
+ if (b == CarriageReturn)
+ continue;
+ if (b == Newline) {
+ return Encoding.ASCII.GetString(mem.ToArray());
+ } else
+ mem.WriteByte(b);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a listing of capabilities that the IMAP server supports. All strings
+ /// in the returned array are guaranteed to be upper-case.
+ /// </summary>
+ /// <exception cref="BadServerResponseException">Thrown if an unexpected response is received
+ /// from the server during the request. The message property of the exception contains the
+ /// error message returned by the server.</exception>
+ /// <returns>A listing of supported capabilities as an array of strings</returns>
+ public string[] Capabilities() {
+ if (capabilities != null)
+ return capabilities;
+ PauseIdling();
+ string tag = GetTag();
+ string command = tag + "CAPABILITY";
+ string response = SendCommandGetResponse(command);
+ /* Server is required to issue untagged capability response */
+ if (response.StartsWith("* CAPABILITY "))
+ response = response.Substring(13);
+ capabilities = response.Trim().Split(' ');
+ /* should return OK */
+ response = GetResponse();
+ ResumeIdling();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ return capabilities;
+ }
+
+ /// <summary>
+ /// Returns whether the specified capability is supported by the server.
+ /// </summary>
+ /// <param name="capability">The capability to probe for (for example "IDLE")</param>
+ /// <exception cref="BadServerResponseException">Thrown if an unexpected response is received
+ /// from the server during the request. The message property of the exception contains
+ /// the error message returned by the server.</exception>
+ /// <returns>Returns true if the specified capability is supported by the server,
+ /// otherwise false is returned.</returns>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="ctor-2"]/*'/>
+ public bool Supports(string capability) {
+ return (capabilities ?? Capabilities()).Contains(capability.ToUpper());
+ }
+
+ /// <summary>
+ /// Changes the name of a mailbox.
+ /// </summary>
+ /// <param name="mailbox">The mailbox to rename.</param>
+ /// <param name="newName">The new name the mailbox will be renamed to.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the mailbox could
+ /// not be renamed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ public void RenameMailbox(string mailbox, string newName) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ PauseIdling();
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "RENAME " +
+ mailbox.QuoteString() + " " + newName.QuoteString());
+ ResumeIdling();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ }
+
+ /// <summary>
+ /// Permanently removes a mailbox.
+ /// </summary>
+ /// <param name="mailbox">Name of the mailbox to remove.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the mailbox could
+ /// not be removed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ public void DeleteMailbox(string mailbox) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ PauseIdling();
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "DELETE " +
+ mailbox.QuoteString());
+ ResumeIdling();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ }
+
+ /// <summary>
+ /// Creates a new mailbox with the given name.
+ /// </summary>
+ /// <param name="mailbox">Name of the mailbox to create.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the mailbox could
+ /// not be created. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ public void CreateMailbox(string mailbox) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ PauseIdling();
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "CREATE " +
+ mailbox.QuoteString());
+ ResumeIdling();
+ if(!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ }
+
+ /// <summary>
+ /// Selects a mailbox so that messages in the mailbox can be accessed.
+ /// </summary>
+ /// <param name="mailbox">The mailbox to select. If this parameter is null, the
+ /// default mailbox is selected.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the mailbox could
+ /// not be selected. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ private void SelectMailbox(string mailbox) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ if (mailbox == null)
+ mailbox = defaultMailbox;
+ /* requested mailbox is already selected */
+ if (selectedMailbox == mailbox)
+ return;
+ PauseIdling();
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "SELECT " +
+ mailbox.QuoteString());
+ /* evaluate untagged data */
+ while (response.StartsWith("*")) {
+ // Fixme: evaluate data
+ response = GetResponse();
+ }
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ selectedMailbox = mailbox;
+ ResumeIdling();
+ }
+
+ /// <summary>
+ /// Permanently removes all messages that have the \Deleted flag set from the
+ /// specified mailbox.
+ /// </summary>
+ /// <param name="mailbox">The mailbox to remove all messages from that have the
+ /// \Deleted flag set. If this parameter is omitted, the value of the DefaultMailbox
+ /// property is used to determine the mailbox to operate on.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the expunge operation could
+ /// not be completed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ public void Expunge(string mailbox = null) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ PauseIdling();
+ SelectMailbox(mailbox);
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "EXPUNGE");
+ /* Server is required to send an untagged response for each message that is
+ * deleted before sending OK */
+ while (response.StartsWith("*"))
+ response = GetResponse();
+ ResumeIdling();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ }
+
+ /// <summary>
+ /// Retrieves status information (total number of messages, number of unread
+ /// messages, etc.) for the specified mailbox. </summary>
+ /// <param name="mailbox">The mailbox to retrieve status information for. If this
+ /// parameter is omitted, the value of the DefaultMailbox property is used to
+ /// determine the mailbox to operate on.</param>
+ /// <returns>A MailboxStatus object containing status information for the
+ /// mailbox.</returns>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the operation could
+ /// not be completed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="GetStatus"]/*'/>
+ public MailboxStatus GetStatus(string mailbox = null) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ PauseIdling();
+ if (mailbox == null)
+ mailbox = defaultMailbox;
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "STATUS " +
+ mailbox.QuoteString() + " (MESSAGES UNSEEN)");
+ int messages = 0, unread = 0;
+ while (response.StartsWith("*")) {
+ Match m = Regex.Match(response, @"\* STATUS.*MESSAGES (\d+)");
+ if (m.Success)
+ messages = Convert.ToInt32(m.Groups[1].Value);
+ m = Regex.Match(response, @"\* STATUS.*UNSEEN (\d+)");
+ if (m.Success)
+ unread = Convert.ToInt32(m.Groups[1].Value);
+ response = GetResponse();
+ }
+ ResumeIdling();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ return new MailboxStatus(messages, unread);
+ }
+
+ /// <summary>
+ /// Searches the specified mailbox for messages that match the given
+ /// searching criteria.
+ /// </summary>
+ /// <param name="criteria">A search criteria expression. Only messages
+ /// that match this expression will be included in the result set returned
+ /// by this method.</param>
+ /// <param name="mailbox">The mailbox that will be searched. If this parameter is
+ /// omitted, the value of the DefaultMailbox property is used to determine the mailbox
+ /// to operate on.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the search could
+ /// not be completed. The message property of the exception contains the error
+ /// message returned by the server.</exception>
+ /// <returns>An array of unique identifier (UID) message attributes which
+ /// can be used with the GetMessage family of methods to download mail
+ /// messages.</returns>
+ /// <remarks>A unique identifier (UID) is a 32-bit value assigned to each
+ /// message which uniquely identifies the message. No two messages share the
+ /// the same UID.</remarks>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="Search"]/*'/>
+ public uint[] Search(SearchCondition criteria, string mailbox = null) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ PauseIdling();
+ SelectMailbox(mailbox);
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "UID SEARCH " +
+ criteria.ToString());
+ List<uint> result = new List<uint>();
+ while (response.StartsWith("*")) {
+ Match m = Regex.Match(response, @"^\* SEARCH (.*)");
+ if (m.Success) {
+ string[] v = m.Groups[1].Value.Trim().Split(' ');
+ foreach (string s in v)
+ result.Add(Convert.ToUInt32(s));
+ }
+ response = GetResponse();
+ }
+ ResumeIdling();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ return result.ToArray();
+ }
+
+ /// <summary>
+ /// Retrieves a mail message by its unique identifier message attribute.
+ /// </summary>
+ /// <param name="uid">The unique identifier of the mail message to retrieve</param>
+ /// <param name="seen">Set this to true to set the \Seen flag for this message
+ /// on the server.</param>
+ /// <param name="mailbox">The mailbox the message will be retrieved from. If this
+ /// parameter is omitted, the value of the DefaultMailbox property is used to
+ /// determine the mailbox to operate on.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the mail message could
+ /// not be fetched. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ /// <returns>An initialized instance of the MailMessage class representing the
+ /// fetched mail message</returns>
+ /// <remarks>A unique identifier (UID) is a 32-bit value assigned to each
+ /// message which uniquely identifies the message. No two messages share the
+ /// the same UID.</remarks>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="GetMessage"]/*'/>
+ public MailMessage GetMessage(uint uid, bool seen = true, string mailbox = null) {
+ MailMessage[] M = GetMessages(new uint[] { uid }, seen, mailbox);
+
+ return M[0];
+ }
+
+ /// <summary>
+ /// Retrieves a set of mail messages by their unique identifier message attributes.
+ /// </summary>
+ /// <param name="uids">An array of unique identifiers of the mail messages to
+ /// retrieve</param>
+ /// <param name="seen">Set this to true to set the \Seen flag for the fetched
+ /// messages on the server.</param>
+ /// <param name="mailbox">The mailbox the messages will be retrieved from. If this
+ /// parameter is omitted, the value of the DefaultMailbox property is used to
+ /// determine the mailbox to operate on.</param>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the mail messages could
+ /// not be fetched. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ /// <returns>An array of initialized instances of the MailMessage class representing
+ /// the fetched mail messages</returns>
+ /// <remarks>A unique identifier (UID) is a 32-bit value assigned to each
+ /// message which uniquely identifies the message. No two messages share the
+ /// the same UID.</remarks>
+ /// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="GetMessages"]/*'/>
+ public MailMessage[] GetMessages(uint[] uids, bool seen = true, string mailbox = null) {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ PauseIdling();
+ SelectMailbox(mailbox);
+ List<MailMessage> messages = new List<MailMessage>();
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "UID FETCH " +
+ string.Join<uint>(",", uids) + " (BODY" + (seen ? null : ".PEEK") + "[])");
+
+ /* ready any untagged responses */
+ while (response.StartsWith("*")) {
+ Match m = Regex.Match(response, @"\* (\d+) FETCH");
+ if (!m.Success)
+ throw new BadServerResponseException(response);
+ uint uid = Convert.ToUInt32(m.Groups[1].Value);
+ /* fetch the actual message header and data */
+ messages.Add(new MessageReader(GetResponse).
+ ReadMailMessage(uid));
+ response = GetResponse();
+ }
+ ResumeIdling();
+ if (!IsResponseOK(response, tag))
+ throw new BadServerResponseException(response);
+ return messages.ToArray();
+ }
+
+ /// <summary>
+ /// Starts receiving of IMAP IDLE notifications from the IMAP server.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">Thrown if the server does
+ /// not support the IMAP4 IDLE command.</exception>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the IDLE operation could
+ /// not be completed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ /// <exception cref="ApplicationException">Thrown if an unexpected program condition
+ /// occured.</exception>
+ /// <remarks>Calling this method when already receiving idle notifications
+ /// has no effect.</remarks>
+ /// <seealso cref="StopIdling"/>
+ /// <seealso cref="PauseIdling"/>
+ /// <seealso cref="ResumeIdling"/>
+ private void StartIdling() {
+ if (idling)
+ return;
+ idling = true;
+ ResumeIdling();
+ }
+
+ /// <summary>
+ /// Stops receiving of IMAP IDLE notifications from the IMAP server.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">Thrown if the server does
+ /// not support the IMAP4 IDLE command.</exception>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the IDLE operation could
+ /// not be completed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ /// <remarks>Calling this method when not receiving idle notifications
+ /// has no effect.</remarks>
+ /// <seealso cref="StartIdling"/>
+ /// <seealso cref="PauseIdling"/>
+ private void StopIdling() {
+ PauseIdling();
+ idling = false;
+ }
+
+ /// <summary>
+ /// Temporarily pauses receiving of IMAP IDLE notifications from the IMAP
+ /// server.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">Thrown if the server does
+ /// not support the IMAP4 IDLE command.</exception>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the IDLE operation could
+ /// not be completed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ /// <remarks>To resume receiving IDLE notifications ResumeIdling must be called
+ /// </remarks>
+ /// <seealso cref="StartIdling"/>
+ /// <seealso cref="ResumeIdling"/>
+ private void PauseIdling() {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ if (!idling)
+ return;
+ if (!Supports("IDLE"))
+ throw new InvalidOperationException("The server does not support the " +
+ "IMAP4 IDLE command");
+ /* Send server "DONE" continuation-command to indicate we no longer wish
+ * to receive idle notifications. The server response is consumed by
+ * the idle thread and signals it to shut down.
+ */
+ SendCommand("DONE");
+
+ /* Wait until idle thread has shutdown */
+ idleThread.Join();
+ idleThread = null;
+ }
+
+ /// <summary>
+ /// Resumes receiving of IMAP IDLE notifications from the IMAP server.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">Thrown if the server does
+ /// not support the IMAP4 IDLE command.</exception>
+ /// <exception cref="NotAuthenticatedException">Thrown if the method was called
+ /// in a non-authenticated state, i.e. before logging into the server with
+ /// valid credentials.</exception>
+ /// <exception cref="BadServerResponseException">Thrown if the IDLE operation could
+ /// not be completed. The message property of the exception contains the error message
+ /// returned by the server.</exception>
+ /// <exception cref="ApplicationException">Thrown if an unexpected program condition
+ /// occured.</exception>
+ /// <remarks>This method is usually called in response to a prior call to the
+ /// PauseIdling method.</remarks>
+ /// <seealso cref="StopIdling"/>
+ private void ResumeIdling() {
+ if (!Authed)
+ throw new NotAuthenticatedException();
+ if (!idling)
+ return;
+ if (!Supports("IDLE"))
+ throw new InvalidOperationException("The server does not support the " +
+ "IMAP4 IDLE command");
+ /* Make sure a mailbox is selected */
+ SelectMailbox(null);
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "IDLE");
+ /* Server must respond with a '+' continuation response */
+ if (!response.StartsWith("+"))
+ throw new BadServerResponseException(response);
+ /* setup and start the idle thread */
+ if (idleThread != null)
+ throw new ApplicationException("idleThread is not null");
+ idleThread = new Thread(IdleLoop);
+ idleThread.Start();
+ }
+
+ /// <summary>
+ /// The main idle loop. Waits for incoming IMAP IDLE notifications and dispatches
+ /// them as events. This runs in its own thread whenever IMAP IDLE
+ /// notifications are to be received.
+ /// </summary>
+ private void IdleLoop() {
+ while (true) {
+ string response = WaitForResponse();
+ /* A request was made to stop idling so quit the thread */
+ if (response.Contains("OK IDLE"))
+ return;
+ Match m = Regex.Match(response, @"\*\s+(\d+)\s+(\w+)");
+ if (!m.Success)
+ continue;
+ /* Examine the notification */
+ uint numberOfMessages = Convert.ToUInt32(m.Groups[1].Value);
+ uint highestUID = 0;
+
+ switch (m.Groups[2].Value.ToUpper()) {
+ case "EXISTS":
+ ThreadPool.QueueUserWorkItem(callback => {
+ newMessageEvent.Raise(this,
+ new IdleMessageEventArgs(numberOfMessages, highestUID, this));
+ });
+ break;
+ case "EXPUNGE":
+ ThreadPool.QueueUserWorkItem(callback => messageDeleteEvent.Raise(
+ this, new IdleMessageEventArgs(numberOfMessages, highestUID, this)));
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Blocks until an IMAP notification has been received while taking
+ /// care of issuing NOOP's to the IMAP server at regular intervals
+ /// </summary>
+ /// <returns>The IMAP command received from the server</returns>
+ private string WaitForResponse() {
+ string response = null;
+ int noopInterval = (int)TimeSpan.FromMinutes(10).TotalMilliseconds;
+ AutoResetEvent ev = new AutoResetEvent(false);
+
+ ThreadPool.QueueUserWorkItem(_ => {
+ try {
+ response = GetResponse();
+ ev.Set();
+ } catch (IOException) {
+ /* Closing _Stream or the underlying _Connection instance will
+ * cause a WSACancelBlockingCall exception on a blocking socket.
+ * This is not an error so just let it pass.
+ */
+ }
+ });
+ if (ev.WaitOne(noopInterval))
+ return response;
+ /* Still here means the NOOP timeout was hit. WorkItem thread is still
+ * in a blocking read which _must_ be consumed.
+ */
+ SendCommand("DONE");
+ ev.WaitOne();
+ if (response.Contains("OK IDLE") == false) {
+ /* Shouldn't happen really */
+ }
+ /* Perform actual NOOP command and resume idling afterwards */
+ IssueNoop();
+ response = SendCommandGetResponse(GetTag() + "IDLE");
+ if (!response.StartsWith("+"))
+ throw new BadServerResponseException(response);
+ /* Go back to receiving IDLE notifications */
+ return WaitForResponse();
+ }
+
+ /// <summary>
+ /// Issues a NOOP command to the IMAP server.
+ /// </summary>
+ /// <remarks>This is needed by the IMAP IDLE mechanism to give the server
+ /// an indication the connection is still active from time to time.
+ /// </remarks>
+ private void IssueNoop() {
+ string tag = GetTag();
+ string response = SendCommandGetResponse(tag + "NOOP");
+ while (!response.StartsWith(tag))
+ response = GetResponse();
+ }
+
+ /// <summary>
+ /// Releases all resources used by this ImapClient object.
+ /// </summary>
+ public void Dispose() {
+ stream.Close();
+ client.Close();
+ stream = null;
+ client = null;
+
+ if (idleThread != null) {
+ idleThread.Abort();
+ idleThread = null;
+ }
+ }
+ }
+}
22 License.md
@@ -0,0 +1,22 @@
+### The MIT License
+
+Copyright (c) 2012 Torben Könke
+
+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.
25 MIMEPart.cs
@@ -0,0 +1,25 @@
+using System.Collections.Specialized;
+
+namespace S22.Imap {
+ /// <summary>
+ /// Represents a part of a MIME multi-part message. Each part consists
+ /// of its own content header and a content body.
+ /// </summary>
+ internal class MIMEPart {
+ /// <summary>
+ /// A collection containing the content header information as
+ /// key-value pairs.
+ /// </summary>
+ public NameValueCollection header {
+ get;
+ set;
+ }
+ /// <summary>
+ /// A string containing the content body of the part.
+ /// </summary>
+ public string body {
+ get;
+ set;
+ }
+ }
+}
38 MailboxStatus.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace S22.Imap {
+ /// <summary>
+ /// Contains status information for a mailbox.
+ /// </summary>
+ public class MailboxStatus {
+ /// <summary>
+ /// Initializes a new MailboxStatus instance with the specified number
+ /// of total and unread messages.
+ /// </summary>
+ /// <param name="Messages"></param>
+ /// <param name="Unread"></param>
+ internal MailboxStatus(int Messages, int Unread) {
+ this.Messages = Messages;
+ this.Unread = Unread;
+ }
+
+ /// <summary>
+ /// The total number of messages in the mailbox.
+ /// </summary>
+ public int Messages {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// The number of unread (unseen) messages in the mailbox.
+ /// </summary>
+ public int Unread {
+ get;
+ private set;
+ }
+ }
+}
336 MessageReader.cs
@@ -0,0 +1,336 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Net.Mail;
+using System.Net.Mime;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace S22.Imap {
+ internal delegate string GetResponseDelegate();
+
+ /// <summary>
+ /// A helper class for reading a mail message and building a MailMessage
+ /// instance out of it.
+ /// </summary>
+ internal class MessageReader {
+ private GetResponseDelegate GetResponse;
+
+ /// <summary>
+ /// Initializes a new instance of the MessageReader class using the
+ /// specified delegate.
+ /// </summary>
+ /// <param name="Delegate">A delegate to the GetResponse method which
+ /// the MessageReader object invokes when it needs to read a line of
+ /// data from the server.</param>
+ public MessageReader(GetResponseDelegate Delegate) {
+ GetResponse = Delegate;
+ }
+
+ /// <summary>
+ /// Reads and processes the message data sent by the server and constructs
+ /// a new MailMessage object from it.
+ /// </summary>
+ /// <param name="uid">The UID of the mail message whose data the server is about
+ /// to send</param>
+ /// <returns>An initialized instance of the MailMessage class representing the
+ /// fetched mail message</returns>
+ public MailMessage ReadMailMessage(uint uid) {
+ NameValueCollection header = ReadMailHeader();
+ NameValueCollection contentType = ParseMIMEField(
+ header["Content-Type"]);
+ string body = null;
+ MIMEPart[] parts = null;
+ if (contentType["boundary"] != null) {
+ parts = ReadMultipartBody(contentType["boundary"]);
+ } else {
+ /* Content-Type does not contain a boundary, assume it's not
+ * a MIME multipart message then
+ */
+ body = ReadMailBody();
+ }
+ return CreateMailmessage(header, body, parts);
+ }
+
+ /// <summary>
+ /// Reads the message header of a mail message and returns it as a
+ /// NameValueCollection.
+ /// </summary>
+ /// <returns>A NameValueCollection containing the header fields as keys
+ /// with their respective values as values.</returns>
+ private NameValueCollection ReadMailHeader() {
+ NameValueCollection header = new NameValueCollection();
+ string response, fieldname = null, fieldvalue = null;
+ while ((response = GetResponse()) != String.Empty) {
+ /* Values may stretch over several lines */
+ if (response[0] == ' ' || response[0] == '\t') {
+ header[fieldname] = header[fieldname] +
+ response.Substring(1).Trim();
+ continue;
+ }
+ /* The mail header consists of field:value pairs */
+ int delimiter = response.IndexOf(':');
+ fieldname = response.Substring(0, delimiter).Trim();
+ fieldvalue = response.Substring(delimiter + 1).Trim();
+ header.Add(fieldname, fieldvalue);
+ }
+ return header;
+ }
+
+ /// <summary>
+ /// Parses a MIME header field which can contain multiple 'parameter = value'
+ /// pairs (such as Content-Type: text/html; charset=iso-8859-1).
+ /// </summary>
+ /// <param name="field">The header field to parse</param>
+ /// <returns>A NameValueCollection containing the parameter names as keys
+ /// with the respective parameter values as values.</returns>
+ /// <remarks>The value of the actual field disregarding the 'parameter = value'
+ /// pairs is stored in the collection under the key "value" (in the above example
+ /// of Content-Type, this would be "text/html").</remarks>
+ private NameValueCollection ParseMIMEField(string field) {
+ NameValueCollection coll = new NameValueCollection();
+ MatchCollection matches = Regex.Matches(field, @"([\w\-]+)=([\w\-\/]+)");
+ foreach (Match m in matches)
+ coll.Add(m.Groups[1].Value, m.Groups[2].Value);
+ Match mvalue = Regex.Match(field, @"^\s*([\w\/]+)");
+ coll.Add("value", mvalue.Success ? mvalue.Groups[1].Value : "");
+ return coll;
+ }
+
+ /// <summary>
+ /// Parses a mail header address-list field such as To, Cc and Bcc which
+ /// can contain multiple email addresses.
+ /// </summary>
+ /// <param name="list">The address-list field to parse</param>
+ /// <returns>An array of strings containing the parsed mail
+ /// addresses.</returns>
+ private string[] ParseAddressList(string list) {
+ List<string> mails = new List<string>();
+ MatchCollection matches = Regex.Matches(list,
+ @"\b([A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4})\b", RegexOptions.IgnoreCase);
+ foreach (Match m in matches)
+ mails.Add(m.Groups[1].Value);
+ return mails.ToArray();
+ }
+
+ /// <summary>
+ /// Parses a mail message identifier from a string.
+ /// </summary>
+ /// <param name="field">The field to parse the message id from</param>
+ /// <exception cref="ArgumentException">Thrown when the field
+ /// argument does not contain a valid message identifier.</exception>
+ /// <returns>The parsed message id</returns>
+ /// <remarks>A message identifier (msg-id) is a globally unique
+ /// identifier for a message.</remarks>
+ private string ParseMessageId(string field) {
+ /* a msg-id is enclosed in < > brackets */
+ Match m = Regex.Match(field, @"<(.+)>");
+ if (m.Success)
+ return m.Groups[1].Value;
+ throw new ArgumentException("The field does not contain a valid message " +
+ "identifier: " + field);
+ }
+
+ /// <summary>
+ /// Reads the plain-text message body of a mail message.
+ /// </summary>
+ /// <returns>The message body of the mail message.</returns>
+ private string ReadMailBody() {
+ string response, body = "";
+ while ((response = GetResponse()) != ")")
+ body = body + response + "\r\n";
+ return body;
+ }
+
+ /// <summary>
+ /// Reads the message body of a MIME multipart message.
+ /// </summary>
+ /// <param name="boundary">The boundary string which separates
+ /// the different parts which make up the multipart-message</param>
+ private MIMEPart[] ReadMultipartBody(string boundary) {
+ List<MIMEPart> parts = new List<MIMEPart>();
+ string s_boundary = "--" + boundary,
+ e_boundary = "--" + boundary + "--";
+ /* skip everything up to the first boundary */
+ string response = GetResponse();
+ while (!response.StartsWith(s_boundary))
+ response = GetResponse();
+ /* read MIME parts enclosed in boundary strings */
+ while (response.StartsWith(s_boundary)) {
+ MIMEPart part = new MIMEPart();
+ /* read content-header of part */
+ part.header = ReadMailHeader();
+ /* read content-body of part */
+ while (!(response = GetResponse()).StartsWith(s_boundary))
+ part.body = part.body + response + "\r\n";
+ /* add MIME part to the list */
+ parts.Add(part);
+ /* if the boundary is actually the end boundary, we're done */
+ if (response == e_boundary)
+ break;
+ }
+ /* next read should return closing bracket from FETCH command */
+ if ((response = GetResponse()) != ")")
+ throw new BadServerResponseException(response);
+ return parts.ToArray();
+ }
+
+ /// <summary>
+ /// Creates a new instance of the MailMessage class and initializes it using
+ /// the specified header and body information.
+ /// </summary>
+ /// <param name="header">A collection of mail and MIME headers</param>
+ /// <param name="body">The mail body. May be null in case the message
+ /// is a MIME multi-part message in which case the MailMessage's body will
+ /// be set to the body of the first MIME part.</param>
+ /// <param name="parts">An array of MIME parts making up the message. If the
+ /// message is not a MIME multi-part message, this can be set to null.
+ /// </param>
+ /// <returns>An initialized instance of the MailMessage class</returns>
+ private MailMessage CreateMailmessage(NameValueCollection header, string body,
+ MIMEPart[] parts) {
+ MailMessage m = new MailMessage();
+ NameValueCollection contentType = ParseMIMEField(
+ header["Content-Type"]);
+ m.Headers.Add(header);
+ if (parts != null) {
+ /* This takes care of setting the Body, BodyEncoding and IsBodyHtml fields also */
+ AddMIMEPartsToMessage(m, parts);
+ } else {
+ /* charset attribute should be part of content-type */
+ try {
+ m.BodyEncoding = Encoding.GetEncoding(
+ contentType["charset"]);
+ } catch {
+ m.BodyEncoding = Encoding.ASCII;
+ }
+ m.Body = body;
+ m.IsBodyHtml = contentType["value"].Contains("text/html");
+ }
+ Match ma = Regex.Match(header["Subject"], @"=\?([A-Za-z0-9\-]+)");
+ if (ma.Success) {
+ /* encoded-word subject */
+ m.SubjectEncoding = Encoding.GetEncoding(
+ ma.Groups[1].Value);
+ m.Subject = Util.DecodeWords(header["Subject"]);
+ } else {
+ m.SubjectEncoding = Encoding.ASCII;
+ m.Subject = header["Subject"];
+ }
+ m.Priority = header["Priority"] != null ?
+ PriorityMapping[header["Priority"]] : MailPriority.Normal;
+ SetAddressFields(m, header);
+ return m;
+ }
+
+ /// <summary>
+ /// A mapping to map MIME priority values to their MailPriority enum
+ /// counterparts.
+ /// </summary>
+ static private Dictionary<string, MailPriority> PriorityMapping =
+ new Dictionary<string, MailPriority>(StringComparer.OrdinalIgnoreCase) {
+ { "non-urgent", MailPriority.Low },
+ { "normal", MailPriority.Normal },
+ { "urgent", MailPriority.High }
+ };
+
+ /// <summary>
+ /// Sets the address fields (From, To, CC, etc.) of a MailMessage
+ /// object using the specified mail message header information.
+ /// </summary>
+ /// <param name="m">The MailMessage instance to operate on</param>
+ /// <param name="header">A collection of mail and MIME headers</param>
+ private void SetAddressFields(MailMessage m, NameValueCollection header) {
+ string[] addr = ParseAddressList(header["To"]);
+ foreach (string s in addr)
+ m.To.Add(s);
+ if (header["Cc"] != null) {
+ addr = ParseAddressList(header["Cc"]);
+ foreach (string s in addr)
+ m.CC.Add(s);
+ }
+ if (header["Bcc"] != null) {
+ addr = ParseAddressList(header["Bcc"]);
+ foreach (string s in addr)
+ m.Bcc.Add(s);
+ }
+ if (header["From"] != null) {
+ addr = ParseAddressList(header["From"]);
+ m.From = new MailAddress(addr.Length > 0 ? addr[0] : "");
+ }
+ if (header["Sender"] != null) {
+ addr = ParseAddressList(header["Sender"]);
+ m.Sender = new MailAddress(addr.Length > 0 ? addr[0] : "");
+ }
+ if (header["Reply-to"] != null) {
+ addr = ParseAddressList(header["Reply-to"]);
+ foreach (string s in addr)
+ m.ReplyToList.Add(s);
+ }
+ }
+
+ /// <summary>
+ /// Adds the parts of a MIME multi-part message to an instance of the
+ /// MailMessage class. MIME parts are either added to the AlternateViews
+ /// or to the Attachments collections depending on their type.
+ /// </summary>
+ /// <param name="m">The MailMessage instance to operate on</param>
+ /// <param name="parts">An array of MIME parts</param>
+ private void AddMIMEPartsToMessage(MailMessage m, MIMEPart[] parts) {
+ for (int i = 0; i < parts.Length; i++) {
+ MIMEPart p = parts[i];
+ NameValueCollection contentType = ParseMIMEField(
+ p.header["Content-Type"]);
+ string transferEnc = p.header["Content-Transfer-Encoding"] ??
+ "none";
+ Encoding encoding = Encoding.GetEncoding(
+ contentType["Charset"] ?? "us-ascii");
+ string body = p.body;
+ /* decode content if it was encoded */
+ switch (transferEnc.ToLower()) {
+ case "quoted-printable":
+ body = Util.QPDecode(p.body, encoding);
+ break;
+ case "base64":
+ body = Util.Base64Decode(p.body, encoding);
+ break;
+ }
+ /* Put the first MIME part into the Body fields of the MailMessage
+ * instance */
+ if (i == 0) {
+ m.Body = body;
+ m.BodyEncoding = encoding;
+ m.IsBodyHtml = contentType["value"].ToLower()
+ .Contains("text/html");
+ continue;
+ }
+ string contentId;
+ try {
+ contentId = ParseMessageId(p.header["Content-Id"]);
+ } catch {
+ contentId = "";
+ }
+ MemoryStream stream = new MemoryStream(encoding.GetBytes(body));
+ NameValueCollection disposition = ParseMIMEField(
+ p.header["Content-Disposition"] ?? "");
+ if (disposition["value"].ToLower() == "attachment") {
+ /* attachments should have the Content-Disposition header set to
+ * "attachment" and possibly contain a name attribute */
+ string filename = disposition["filename"] ?? ("attachment" +
+ i.ToString());
+ Attachment attachment = new Attachment(stream, filename);
+ attachment.ContentId = contentId;
+ attachment.ContentType = new ContentType(p.header["Content-Type"]);
+ m.Attachments.Add(attachment);
+ } else {
+ AlternateView view = new AlternateView(stream,
+ new ContentType(p.header["Content-Type"]));
+ view.ContentId = contentId;
+ view.ContentType = new ContentType(p.header["Content-Type"]);
+ m.AlternateViews.Add(view);
+ }
+ }
+ }
+ }
+}
36 Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("S22.Imap")]
+[assembly: AssemblyDescription("A library for communicating with an IMAP mail server")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("None")]
+[assembly: AssemblyProduct("S22.Imap")]
+[assembly: AssemblyCopyright("Copyright © Torben Könke 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("df1a2cd6-fa4f-4398-8d3a-0a4f61225203")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
47 Readme.md
@@ -0,0 +1,47 @@
+### Introduction
+
+This repository contains an easy-to-use and well-documented .NET assembly for communicating with and
+receiving electronic mail from an Internet Message Access Protocol (IMAP) server.
+
+
+### Usage & Examples
+
+To use the library add the S22.Imap.dll assembly to your project references in Visual Studio. Here's
+a simple example that initializes a new instance of the ImapClient class and connects to Gmail's
+IMAP server:
+
+ using System;
+ using S22.Imap;
+
+ namespace Test {
+ class Program {
+ static void Main(string[] args) {
+ /* connect on port 993 using SSL */
+ using (ImapClient Client = new ImapClient("imap.gmail.com", 993, true))
+ {
+ Console.WriteLine("We are connected!");
+ }
+ }
+ }
+ }
+
+Please see the [documentation](http://smiley22.github.com/S22.Imap/Documentation/) for further details on using
+the classes and methods exposed by the S22.Imap namespace. Plenty of example codes are provided.
+
+
+### Credits
+
+This library is copyright © 2012 Torben Könke.
+
+Parts of this library are heavily based on the AE.Net.Mail project (copyright © 2012 Andy Edinborough).
+
+
+### License
+
+This library is released under the [MIT license](https://github.com/smiley22/smiley22.github.com/blob/master/License.md).
+
+
+### Bug reports
+
+Please send your bug reports and questions to [smileytwentytwo@gmail.com](mailto:smileytwentytwo@gmail.com) or create a new
+issue on the GitHub project homepage.
71 S22.Imap.csproj
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{369C32A5-E099-4BD5-BBBF-51713947CA99}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>S22.Imap</RootNamespace>
+ <AssemblyName>S22.Imap</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <DocumentationFile>bin\Debug\S22.Imap.XML</DocumentationFile>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="AuthMethod.cs" />
+ <Compile Include="IdleEvents.cs" />
+ <Compile Include="ImapClient.cs" />
+ <Compile Include="Exceptions.cs" />
+ <Compile Include="MailboxStatus.cs" />
+ <Compile Include="MessageReader.cs" />
+ <Compile Include="MIMEPart.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="SearchCondition.cs" />
+ <Compile Include="Util.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="Examples.xml">
+ <SubType>Designer</SubType>
+ </Content>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="Readme.md" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
30 S22.Imap.sln
@@ -0,0 +1,30 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S22.Imap", "S22.Imap.csproj", "{369C32A5-E099-4BD5-BBBF-51713947CA99}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Release|Any CPU.Build.0 = Release|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {369C32A5-E099-4BD5-BBBF-51713947CA99}.Release|x86.ActiveCfg = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
378 SearchCondition.cs
@@ -0,0 +1,378 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace S22.Imap {
+ /// <summary>
+ /// Chainable search conditions to be used with the Search method.
+ /// </summary>
+ public class SearchCondition {
+ /// <summary>
+ /// Finds messages that contain the specified string in the header or body of the
+ /// message.
+ /// </summary>
+ /// <param name="text">String to search messages for</param>
+ /// <returns>A SearchCondition object representing the "text" search criterion</returns>
+ public static SearchCondition Text(string text) {
+ return new SearchCondition { Field = Fields.Text, Value = text };
+ }
+ /// <summary>
+ /// Finds messages that contain the specified string in the envelope structure's
+ /// BCC field.
+ /// </summary>
+ /// <param name="text">String to search the envelope structure's BCC field for</param>
+ /// <returns>A SearchCondition object representing the "BCC" search criterion</returns>
+ public static SearchCondition BCC(string text) {
+ return new SearchCondition { Field = Fields.BCC, Value = text };
+ }
+ /// <summary>
+ /// Finds messages whose internal date (disregarding time and timezone) is
+ /// earlier than the specified date.
+ /// </summary>
+ /// <param name="date">Date to compare the message's internal date with</param>
+ /// <returns>A SearchCondition object representing the "Before" search criterion</returns>
+ public static SearchCondition Before(DateTime date) {
+ return new SearchCondition { Field = Fields.Before, Value = date };
+ }
+ /// <summary>
+ /// Finds messages that contain the specified string in the body of the
+ /// message.
+ /// </summary>
+ /// <param name="text">String to search the message body for</param>
+ /// <returns>A SearchCondition object representing the "Body" search criterion</returns>
+ public static SearchCondition Body(string text) {
+ return new SearchCondition { Field = Fields.Body, Value = text };
+ }
+ /// <summary>
+ /// Finds messages that contain the specified string in the envelope structure's
+ /// CC field.
+ /// </summary>
+ /// <param name="text">String to search the envelope structure's CC field for</param>
+ /// <returns>A SearchCondition object representing the "CC" search criterion</returns>
+ public static SearchCondition Cc(string text) {
+ return new SearchCondition { Field = Fields.Cc, Value = text };
+ }
+ /// <summary>
+ /// Finds messages that contain the specified string in the envelope structure's
+ /// FROM field.
+ /// </summary>
+ /// <param name="text">String to search the envelope structure's FROM field for</param>
+ /// <returns>A SearchCondition object representing the "FROM" search criterion</returns>
+ public static SearchCondition From(string text) {
+ return new SearchCondition { Field = Fields.From, Value = text };
+ }
+ /// <summary>
+ /// Finds messages that have a header with the specified field-name and that
+ /// contains the specified string in the text of the header.
+ /// </summary>
+ /// <param name="name">field-name of the header to search for</param>
+ /// <param name="text">String to search for in the text of the header</param>
+ /// <returns>A SearchCondition object representing the "HEADER" search
+ /// criterion</returns>
+ /// <remarks>
+ /// If the string to search is zero-length, this matches all messages
+ /// that have a header line with the specified field-name regardless of the
+ /// contents.
+ /// </remarks>
+ public static SearchCondition Header(string name, string text) {
+ return new SearchCondition { Field = Fields.Header,
+ Value = name + " " + text.QuoteString() };
+ }
+ /// <summary>
+ /// Finds messages with the specified keyword flag set.
+ /// </summary>
+ /// <param name="text">Keyword flag to search for</param>
+ /// <returns>A SearchCondition object representing the "KEYWORD" search
+ /// criterion</returns>
+ public static SearchCondition Keyword(string text) {
+ return new SearchCondition { Field = Fields.Keyword, Value = text };
+ }
+ /// <summary>
+ /// Finds messages with a size larger than the specified number of bytes.
+ /// </summary>
+ /// <param name="size">Minimum size, in bytes a message must have to be
+ /// included in the result set</param>
+ /// <returns>A SearchCondition object representing the "LARGER" search
+ /// criterion</returns>
+ public static SearchCondition Larger(long size) {
+ return new SearchCondition { Field = Fields.Larger, Value = size };
+ }
+ /// <summary>
+ /// Finds messages with a size smaller than the specified number of bytes.
+ /// </summary>
+ /// <param name="size">Maximum size, in bytes a message must have to be
+ /// included in the result set</param>
+ /// <returns>A SearchCondition object representing the "SMALLER" search
+ /// criterion</returns>
+ public static SearchCondition Smaller(long size) {
+ return new SearchCondition { Field = Fields.Smaller, Value = size };
+ }
+ /// <summary>
+ /// Finds messages whose Date: header (disregarding time and timezone) is
+ /// earlier than the specified date.
+ /// </summary>
+ /// <param name="date">Date to compare the Date: header field with.</param>
+ /// <returns>A SearchCondition object representing the "SENTBEFORE" search
+ /// criterion</returns>
+ public static SearchCondition SentBefore(DateTime date) {
+ return new SearchCondition { Field = Fields.SentBefore, Value = date };
+ }
+ /// <summary>
+ /// Finds messages whose Date: header (disregarding time and timezone) is
+ /// within the specified date.
+ /// </summary>
+ /// <param name="date">Date to compare the Date: header field with.</param>
+ /// <returns>A SearchCondition object representing the "SENTON" search
+ /// criterion</returns>
+ public static SearchCondition SentOn(DateTime date) {
+ return new SearchCondition { Field = Fields.SentOn, Value = date };
+ }
+ /// <summary>
+ /// Finds messages whose Date: header (disregarding time and timezone) is
+ /// within or later than the specified date.
+ /// </summary>
+ /// <param name="date">Date to compare the Date: header field with.</param>
+ /// <returns>A SearchCondition object representing the "SENTSINCE" search
+ /// criterion</returns>
+ public static SearchCondition SentSince(DateTime date) {
+ return new SearchCondition { Field = Fields.SentSince, Value = date };
+ }
+ /// <summary>
+ /// Finds messages that contain the specified string in the envelope
+ /// structure's SUBJECT field.
+ /// </summary>
+ /// <param name="text">String to search the envelope structure's SUBJECT
+ /// field for</param>
+ /// <returns>A SearchCondition object representing the "SUBJECT" search
+ /// criterion</returns>
+ public static SearchCondition Subject(string text) {
+ return new SearchCondition { Field = Fields.Subject, Value = text };
+ }
+ /// <summary>
+ /// Finds messages that contain the specified string in the envelope
+ /// structure's TO field.
+ /// </summary>
+ /// <param name="text">String to search the envelope structure's TO
+ /// field for</param>
+ /// <returns>A SearchCondition object representing the "TO" search
+ /// criterion</returns>
+ public static SearchCondition To(string text) {
+ return new SearchCondition { Field = Fields.To, Value = text };
+ }
+ /// <summary>
+ /// Finds messages with unique identifiers corresponding to the specified
+ /// unique identifier set. Sequence set ranges are permitted.
+ /// </summary>
+ /// <param name="ids">String of whitespace-separated list of unique
+ /// identifiers to search for</param>
+ /// <returns>A SearchCondition object representing the "UID" search
+ /// criterion</returns>
+ public static SearchCondition UID(string ids) {
+ return new SearchCondition { Field = Fields.UID, Value = ids };
+ }
+ /// <summary>
+ /// Finds messages that do not have the specified keyword flag set.
+ /// </summary>
+ /// <param name="text">A valid IMAP keyword flag</param>
+ /// <returns>A SearchCondition object representing the "UNKEYWORD"
+ /// search criterion</returns>
+ public static SearchCondition Unkeyword(string text) {
+ return new SearchCondition { Field = Fields.Unkeyword, Value = text };
+ }
+ /// <summary>
+ /// Finds messages with the \Answered flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "ANSWERED" search
+ /// criterion</returns>
+ public static SearchCondition Answered() {
+ return new SearchCondition { Field = Fields.Answered };
+ }
+ /// <summary>
+ /// Finds messages with the \Deleted flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "DELETED" search
+ /// criterion</returns>
+ public static SearchCondition Deleted() {
+ return new SearchCondition { Field = Fields.Deleted };
+ }
+ /// <summary>
+ /// Finds messages with the \Draft flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "DRAFT" search
+ /// criterion</returns>
+ public static SearchCondition Draft() {
+ return new SearchCondition { Field = Fields.Draft };
+ }
+ /// <summary>
+ /// Finds messages with the \Flagged flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "FLAGGED" search
+ /// criterion</returns>
+ public static SearchCondition Flagged() {
+ return new SearchCondition { Field = Fields.Flagged };
+ }
+ /// <summary>
+ /// Finds messages that have the \Recent flag set but not the \Seen flag.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "NEW" search
+ /// criterion</returns>
+ public static SearchCondition New() {
+ return new SearchCondition { Field = Fields.New };
+ }
+ /// <summary>
+ /// Finds messages that do not have the \Recent flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "OLD" search
+ /// criterion</returns>
+ public static SearchCondition Old() {
+ return new SearchCondition { Field = Fields.Old };
+ }
+ /// <summary>
+ /// Finds messages that have the \Recent flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "RECENT" search
+ /// criterion</returns>
+ public static SearchCondition Recent() {
+ return new SearchCondition { Field = Fields.Recent };
+ }
+ /// <summary>
+ /// Finds messages that have the \Seen flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "SEEN" search
+ /// criterion</returns>
+ public static SearchCondition Seen() {
+ return new SearchCondition { Field = Fields.Seen };
+ }
+ /// <summary>
+ /// Finds messages that do not have the \Answered flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "UNANSWERED" search
+ /// criterion</returns>
+ public static SearchCondition Unanswered() {
+ return new SearchCondition { Field = Fields.Unanswered };
+ }
+ /// <summary>
+ /// Finds messages that do not have the \Deleted flag set.
+ /// </summary>
+ /// <returns>A SearchCondition object representing the "UNDELETED" search
+ /// criterion</returns>
+ public static SearchCondition Undeleted() {
+ return new SearchCondition { Field = Fields.Undeleted };
+ }
+ /// <summary>
+ /// Finds messages that do not have the \Draft flag set.