OpenSim.Modules.EMail/src/MailKit/Net/Pop3/Pop3Client.cs

3402 lines
121 KiB
C#

//
// Pop3Client.cs
//
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
//
// Copyright (c) 2013-2020 .NET Foundation and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net.Security;
using System.Globalization;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using MimeKit;
using MimeKit.IO;
using MailKit.Security;
using SslStream = MailKit.Net.SslStream;
using NetworkStream = MailKit.Net.NetworkStream;
namespace MailKit.Net.Pop3 {
/// <summary>
/// A POP3 client that can be used to retrieve messages from a server.
/// </summary>
/// <remarks>
/// The <see cref="Pop3Client"/> class supports both the "pop" and "pops" protocols. The "pop" protocol
/// makes a clear-text connection to the POP3 server and does not use SSL or TLS unless the POP3 server
/// supports the <a href="https://tools.ietf.org/html/rfc2595">STLS</a> extension. The "pops" protocol,
/// however, connects to the POP3 server using an SSL-wrapped connection.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="DownloadMessages"/>
/// </example>
public partial class Pop3Client : MailSpool, IPop3Client
{
[Flags]
enum ProbedCapabilities : byte {
None = 0,
Top = (1 << 0),
UIDL = (1 << 1)
}
readonly MimeParser parser = new MimeParser (Stream.Null);
readonly Pop3Engine engine;
ProbedCapabilities probed;
bool disposed, disconnecting, secure, utf8;
int timeout = 2 * 60 * 1000;
long octets;
int total;
/// <summary>
/// Initializes a new instance of the <see cref="MailKit.Net.Pop3.Pop3Client"/> class.
/// </summary>
/// <remarks>
/// Before you can retrieve messages with the <see cref="Pop3Client"/>, you must first call
/// one of the <a href="Overload_MailKit_Net_Pop3_Pop3Client_Connect.htm">Connect</a> methods
/// and authenticate using one of the
/// <a href="Overload_MailKit_Net_Pop3_Pop3Client_Authenticate.htm">Authenticate</a> methods.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="ProtocolLogger"/>
/// </example>
/// <param name="protocolLogger">The protocol logger.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="protocolLogger"/> is <c>null</c>.
/// </exception>
public Pop3Client (IProtocolLogger protocolLogger) : base (protocolLogger)
{
engine = new Pop3Engine ();
}
/// <summary>
/// Initializes a new instance of the <see cref="MailKit.Net.Pop3.Pop3Client"/> class.
/// </summary>
/// <remarks>
/// Before you can retrieve messages with the <see cref="Pop3Client"/>, you must first call
/// one of the <a href="Overload_MailKit_Net_Pop3_Pop3Client_Connect.htm">Connect</a> methods
/// and authenticate using one of the
/// <a href="Overload_MailKit_Net_Pop3_Pop3Client_Authenticate.htm">Authenticate</a> methods.
/// </remarks>
public Pop3Client () : this (new NullProtocolLogger ())
{
}
/// <summary>
/// Gets an object that can be used to synchronize access to the POP3 server.
/// </summary>
/// <remarks>
/// <para>Gets an object that can be used to synchronize access to the POP3 server.</para>
/// <para>When using the non-Async methods from multiple threads, it is important to lock the
/// <see cref="SyncRoot"/> object for thread safety when using the synchronous methods.</para>
/// </remarks>
/// <value>The lock object.</value>
public override object SyncRoot {
get { return engine; }
}
/// <summary>
/// Gets the protocol supported by the message service.
/// </summary>
/// <remarks>
/// Gets the protocol supported by the message service.
/// </remarks>
/// <value>The protocol.</value>
protected override string Protocol {
get { return "pop"; }
}
/// <summary>
/// Gets the capabilities supported by the POP3 server.
/// </summary>
/// <remarks>
/// The capabilities will not be known until a successful connection has been made
/// and may change once the client is authenticated.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="Capabilities"/>
/// </example>
/// <value>The capabilities.</value>
/// <exception cref="System.ArgumentException">
/// Capabilities cannot be enabled, they may only be disabled.
/// </exception>
public Pop3Capabilities Capabilities {
get { return engine.Capabilities; }
set {
if ((engine.Capabilities | value) > engine.Capabilities)
throw new ArgumentException ("Capabilities cannot be enabled, they may only be disabled.", nameof (value));
engine.Capabilities = value;
}
}
/// <summary>
/// Gets the expiration policy.
/// </summary>
/// <remarks>
/// <para>If the server supports the EXPIRE capability (<see cref="Pop3Capabilities.Expire"/>), the value
/// of the <see cref="ExpirePolicy"/> property will reflect the value advertized by the server.</para>
/// <para>A value of <c>-1</c> indicates that messages will never expire.</para>
/// <para>A value of <c>0</c> indicates that messages that have been retrieved during the current session
/// will be purged immediately after the connection is closed via the <c>QUIT</c> command.</para>
/// <para>Values larger than <c>0</c> indicate the minimum number of days that the server will retain
/// messages which have been retrieved.</para>
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="Capabilities"/>
/// </example>
/// <value>The expiration policy.</value>
public int ExpirePolicy {
get { return engine.ExpirePolicy; }
}
/// <summary>
/// Gets the implementation details of the server.
/// </summary>
/// <remarks>
/// If the server advertizes its implementation details, this value will be set to a string containing the
/// information details provided by the server.
/// </remarks>
/// <value>The implementation details.</value>
public string Implementation {
get { return engine.Implementation; }
}
/// <summary>
/// Gets the minimum delay, in milliseconds, between logins.
/// </summary>
/// <remarks>
/// If the server supports the LOGIN-DELAY capability (<see cref="Pop3Capabilities.LoginDelay"/>), this value
/// will be set to the minimum number of milliseconds that the client must wait between logins.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="Capabilities"/>
/// </example>
/// <value>The login delay.</value>
public int LoginDelay {
get { return engine.LoginDelay; }
}
void CheckDisposed ()
{
if (disposed)
throw new ObjectDisposedException (nameof (Pop3Client));
}
void CheckConnected ()
{
if (!IsConnected)
throw new ServiceNotConnectedException ("The Pop3Client is not connected.");
}
void CheckAuthenticated ()
{
if (!IsAuthenticated)
throw new ServiceNotAuthenticatedException ("The Pop3Client has not been authenticated.");
}
bool ValidateRemoteCertificate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (ServerCertificateValidationCallback != null)
return ServerCertificateValidationCallback (engine.Uri.Host, certificate, chain, sslPolicyErrors);
#if !NETSTANDARD1_3 && !NETSTANDARD1_6
if (ServicePointManager.ServerCertificateValidationCallback != null)
return ServicePointManager.ServerCertificateValidationCallback (engine.Uri.Host, certificate, chain, sslPolicyErrors);
#endif
return DefaultServerCertificateValidationCallback (engine.Uri.Host, certificate, chain, sslPolicyErrors);
}
static Exception CreatePop3Exception (Pop3Command pc)
{
var command = pc.Command.Split (' ')[0].TrimEnd ();
var message = string.Format ("POP3 server did not respond with a +OK response to the {0} command.", command);
if (pc.Status == Pop3CommandStatus.Error)
return new Pop3CommandException (message, pc.StatusText);
return new Pop3ProtocolException (message);
}
static ProtocolException CreatePop3ParseException (Exception innerException, string format, params object[] args)
{
return new Pop3ProtocolException (string.Format (CultureInfo.InvariantCulture, format, args), innerException);
}
static ProtocolException CreatePop3ParseException (string format, params object[] args)
{
return new Pop3ProtocolException (string.Format (CultureInfo.InvariantCulture, format, args));
}
async Task SendCommandAsync (bool doAsync, CancellationToken token, string command)
{
var pc = engine.QueueCommand (token, null, Encoding.ASCII, command);
int id;
do {
if (doAsync)
id = await engine.IterateAsync ().ConfigureAwait (false);
else
id = engine.Iterate ();
} while (id < pc.Id);
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
}
Task<string> SendCommandAsync (bool doAsync, CancellationToken token, string format, params object[] args)
{
return SendCommandAsync (doAsync, token, Encoding.ASCII, format, args);
}
async Task<string> SendCommandAsync (bool doAsync, CancellationToken token, Encoding encoding, string format, params object[] args)
{
string okText = string.Empty;
int id;
var pc = engine.QueueCommand (token, (pop3, cmd, text, xdoAsync) => {
if (cmd.Status == Pop3CommandStatus.Ok)
okText = text;
return Task.FromResult (true);
}, encoding, format, args);
do {
if (doAsync)
id = await engine.IterateAsync ().ConfigureAwait (false);
else
id = engine.Iterate ();
} while (id < pc.Id);
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
return okText;
}
#region IMailService implementation
/// <summary>
/// Gets the authentication mechanisms supported by the POP3 server.
/// </summary>
/// <remarks>
/// <para>The authentication mechanisms are queried as part of the
/// connection process.</para>
/// <para>Servers that do not support the SASL capability will typically
/// support either the <c>APOP</c> authentication mechanism
/// (<see cref="Pop3Capabilities.Apop"/>) or the ability to login using the
/// <c>USER</c> and <c>PASS</c> commands (<see cref="Pop3Capabilities.User"/>).
/// </para>
/// <note type="tip"><para>To prevent the usage of certain authentication mechanisms,
/// simply remove them from the <see cref="AuthenticationMechanisms"/> hash set
/// before authenticating.</para>
/// <para>In the case of the APOP authentication mechanism, remove it from the
/// <see cref="Capabilities"/> property instead.</para></note>
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="Capabilities"/>
/// </example>
/// <value>The authentication mechanisms.</value>
public override HashSet<string> AuthenticationMechanisms {
get { return engine.AuthenticationMechanisms; }
}
/// <summary>
/// Gets or sets the timeout for network streaming operations, in milliseconds.
/// </summary>
/// <remarks>
/// Gets or sets the underlying socket stream's <see cref="System.IO.Stream.ReadTimeout"/>
/// and <see cref="System.IO.Stream.WriteTimeout"/> values.
/// </remarks>
/// <value>The timeout in milliseconds.</value>
public override int Timeout {
get { return timeout; }
set {
if (IsConnected && engine.Stream.CanTimeout) {
engine.Stream.WriteTimeout = value;
engine.Stream.ReadTimeout = value;
}
timeout = value;
}
}
/// <summary>
/// Gets whether or not the client is currently connected to an POP3 server.
/// </summary>
/// <remarks>
/// <para>The <see cref="IsConnected"/> state is set to <c>true</c> immediately after
/// one of the <a href="Overload_MailKit_Net_Pop3_Pop3Client_Connect.htm">Connect</a>
/// methods succeeds and is not set back to <c>false</c> until either the client
/// is disconnected via <see cref="Disconnect(bool,CancellationToken)"/> or until a
/// <see cref="Pop3ProtocolException"/> is thrown while attempting to read or write to
/// the underlying network socket.</para>
/// <para>When an <see cref="Pop3ProtocolException"/> is caught, the connection state of the
/// <see cref="Pop3Client"/> should be checked before continuing.</para>
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="ExceptionHandling"/>
/// </example>
/// <value><c>true</c> if the client is connected; otherwise, <c>false</c>.</value>
public override bool IsConnected {
get { return engine.IsConnected; }
}
/// <summary>
/// Get whether or not the connection is secure (typically via SSL or TLS).
/// </summary>
/// <remarks>
/// Gets whether or not the connection is secure (typically via SSL or TLS).
/// </remarks>
/// <value><c>true</c> if the connection is secure; otherwise, <c>false</c>.</value>
public override bool IsSecure {
get { return IsConnected && secure; }
}
/// <summary>
/// Get whether or not the client is currently authenticated with the POP3 server.
/// </summary>
/// <remarks>
/// <para>Gets whether or not the client is currently authenticated with the POP3 server.</para>
/// <para>To authenticate with the POP3 server, use one of the
/// <a href="Overload_MailKit_Net_Pop3_Pop3Client_Authenticate.htm">Authenticate</a> methods.</para>
/// </remarks>
/// <value><c>true</c> if the client is connected; otherwise, <c>false</c>.</value>
public override bool IsAuthenticated {
get { return engine.State == Pop3EngineState.Transaction; }
}
async Task UpdateMessageCountAsync (bool doAsync, CancellationToken cancellationToken)
{
var pc = engine.QueueCommand (cancellationToken, (pop3, cmd, text, xdoAsync) => {
if (cmd.Status != Pop3CommandStatus.Ok)
return Task.FromResult (false);
// the response should be "<count> <total size>"
var tokens = text.Split (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length < 2) {
cmd.Exception = CreatePop3ParseException ("Pop3 server returned an incomplete response to the STAT command: {0}", text);
return Task.FromResult (false);
}
if (!int.TryParse (tokens[0], NumberStyles.None, CultureInfo.InvariantCulture, out total) || total < 0) {
cmd.Exception = CreatePop3ParseException ("Pop3 server returned an invalid response to the STAT command: {0}", text);
return Task.FromResult (false);
}
if (!long.TryParse (tokens[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out octets)) {
cmd.Exception = CreatePop3ParseException ("Pop3 server returned an invalid response to the STAT command: {0}", text);
return Task.FromResult (false);
}
return Task.FromResult (true);
}, "STAT");
int id;
do {
if (doAsync)
id = await engine.IterateAsync ().ConfigureAwait (false);
else
id = engine.Iterate ();
} while (id < pc.Id);
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
if (pc.Exception != null)
throw pc.Exception;
}
async Task ProbeCapabilitiesAsync (bool doAsync, CancellationToken cancellationToken)
{
if ((engine.Capabilities & Pop3Capabilities.UIDL) == 0 && (probed & ProbedCapabilities.UIDL) == 0) {
// if the message count is > 0, we can probe the UIDL command
if (total > 0) {
try {
var ctx = new MessageUidContext (this, 1);
await ctx.GetUidAsync (doAsync, cancellationToken).ConfigureAwait (false);
} catch (NotSupportedException) {
}
}
}
}
async Task QueryCapabilitiesAsync (bool doAsync, CancellationToken cancellationToken)
{
if (doAsync)
await engine.QueryCapabilitiesAsync (cancellationToken).ConfigureAwait (false);
else
engine.QueryCapabilities (cancellationToken);
}
class SaslAuthContext
{
readonly SaslMechanism mechanism;
readonly Pop3Client client;
public SaslAuthContext (Pop3Client client, SaslMechanism mechanism)
{
this.mechanism = mechanism;
this.client = client;
}
public string AuthMessage {
get; private set;
}
Pop3Engine Engine {
get { return client.engine; }
}
async Task OnDataReceived (Pop3Engine pop3, Pop3Command pc, string text, bool doAsync)
{
while (pc.Status == Pop3CommandStatus.Continue && !mechanism.IsAuthenticated) {
var challenge = mechanism.Challenge (text);
var buf = Encoding.ASCII.GetBytes (challenge + "\r\n");
string response;
if (doAsync) {
await pop3.Stream.WriteAsync (buf, 0, buf.Length, pc.CancellationToken).ConfigureAwait (false);
await pop3.Stream.FlushAsync (pc.CancellationToken).ConfigureAwait (false);
response = (await pop3.ReadLineAsync (pc.CancellationToken).ConfigureAwait (false)).TrimEnd ();
} else {
pop3.Stream.Write (buf, 0, buf.Length, pc.CancellationToken);
pop3.Stream.Flush (pc.CancellationToken);
response = pop3.ReadLine (pc.CancellationToken).TrimEnd ();
}
pc.Status = Pop3Engine.GetCommandStatus (response, out text);
pc.StatusText = text;
if (pc.Status == Pop3CommandStatus.ProtocolError)
throw new Pop3ProtocolException (string.Format ("Unexpected response from server: {0}", response));
}
AuthMessage = text;
}
public async Task<Pop3Command> AuthenticateAsync (bool doAsync, CancellationToken cancellationToken)
{
var pc = Engine.QueueCommand (cancellationToken, OnDataReceived, "AUTH {0}", mechanism.MechanismName);
int id;
AuthMessage = string.Empty;
do {
if (doAsync)
id = await Engine.IterateAsync ().ConfigureAwait (false);
else
id = Engine.Iterate ();
} while (id < pc.Id);
return pc;
}
}
async Task AuthenticateAsync (SaslMechanism mechanism, bool doAsync, CancellationToken cancellationToken)
{
if (mechanism == null)
throw new ArgumentNullException (nameof (mechanism));
if (!IsConnected)
throw new ServiceNotConnectedException ("The Pop3Client must be connected before you can authenticate.");
if (IsAuthenticated)
throw new InvalidOperationException ("The Pop3Client is already authenticated.");
CheckDisposed ();
cancellationToken.ThrowIfCancellationRequested ();
mechanism.Uri = new Uri ("pop://" + engine.Uri.Host);
var ctx = new SaslAuthContext (this, mechanism);
var pc = await ctx.AuthenticateAsync (doAsync, cancellationToken).ConfigureAwait (false);
if (pc.Status == Pop3CommandStatus.Error)
throw new AuthenticationException ();
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
if (pc.Exception != null)
throw pc.Exception;
engine.State = Pop3EngineState.Transaction;
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
await UpdateMessageCountAsync (doAsync, cancellationToken).ConfigureAwait (false);
await ProbeCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
OnAuthenticated (ctx.AuthMessage);
}
/// <summary>
/// Authenticate using the specified SASL mechanism.
/// </summary>
/// <remarks>
/// <para>Authenticates using the specified SASL mechanism.</para>
/// <para>For a list of available SASL authentication mechanisms supported by the server,
/// check the <see cref="AuthenticationMechanisms"/> property after the service has been
/// connected.</para>
/// </remarks>
/// <param name="mechanism">The SASL mechanism.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="mechanism"/> is <c>null</c>.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// The <see cref="Pop3Client"/> is already authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="MailKit.Security.AuthenticationException">
/// Authentication using the supplied credentials has failed.
/// </exception>
/// <exception cref="MailKit.Security.SaslException">
/// A SASL authentication error occurred.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// A POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// An POP3 protocol error occurred.
/// </exception>
public override void Authenticate (SaslMechanism mechanism, CancellationToken cancellationToken = default (CancellationToken))
{
AuthenticateAsync (mechanism, false, cancellationToken).GetAwaiter ().GetResult ();
}
async Task AuthenticateAsync (Encoding encoding, ICredentials credentials, bool doAsync, CancellationToken cancellationToken)
{
if (encoding == null)
throw new ArgumentNullException (nameof (encoding));
if (credentials == null)
throw new ArgumentNullException (nameof (credentials));
if (!IsConnected)
throw new ServiceNotConnectedException ("The Pop3Client must be connected before you can authenticate.");
if (IsAuthenticated)
throw new InvalidOperationException ("The Pop3Client is already authenticated.");
CheckDisposed ();
var saslUri = new Uri ("pop://" + engine.Uri.Host);
string userName, password, message = null;
NetworkCredential cred;
if ((engine.Capabilities & Pop3Capabilities.Apop) != 0) {
cred = credentials.GetCredential (saslUri, "APOP");
userName = utf8 ? SaslMechanism.SaslPrep (cred.UserName) : cred.UserName;
password = utf8 ? SaslMechanism.SaslPrep (cred.Password) : cred.Password;
var challenge = engine.ApopToken + password;
var md5sum = new StringBuilder ();
byte[] digest;
using (var md5 = MD5.Create ())
digest = md5.ComputeHash (encoding.GetBytes (challenge));
for (int i = 0; i < digest.Length; i++)
md5sum.Append (digest[i].ToString ("x2"));
try {
message = await SendCommandAsync (doAsync, cancellationToken, encoding, "APOP {0} {1}", userName, md5sum).ConfigureAwait (false);
engine.State = Pop3EngineState.Transaction;
} catch (Pop3CommandException) {
}
if (engine.State == Pop3EngineState.Transaction) {
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
await UpdateMessageCountAsync (doAsync, cancellationToken).ConfigureAwait (false);
await ProbeCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
OnAuthenticated (message ?? string.Empty);
return;
}
}
if ((engine.Capabilities & Pop3Capabilities.Sasl) != 0) {
foreach (var authmech in SaslMechanism.AuthMechanismRank) {
SaslMechanism sasl;
if (!engine.AuthenticationMechanisms.Contains (authmech))
continue;
if ((sasl = SaslMechanism.Create (authmech, saslUri, encoding, credentials)) == null)
continue;
cancellationToken.ThrowIfCancellationRequested ();
var ctx = new SaslAuthContext (this, sasl);
var pc = await ctx.AuthenticateAsync (doAsync, cancellationToken).ConfigureAwait (false);
if (pc.Status == Pop3CommandStatus.Error)
continue;
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
if (pc.Exception != null)
throw pc.Exception;
engine.State = Pop3EngineState.Transaction;
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
await UpdateMessageCountAsync (doAsync, cancellationToken).ConfigureAwait (false);
await ProbeCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
OnAuthenticated (ctx.AuthMessage);
return;
}
}
// fall back to the classic USER & PASS commands...
cred = credentials.GetCredential (saslUri, "DEFAULT");
userName = utf8 ? SaslMechanism.SaslPrep (cred.UserName) : cred.UserName;
password = utf8 ? SaslMechanism.SaslPrep (cred.Password) : cred.Password;
try {
await SendCommandAsync (doAsync, cancellationToken, encoding, "USER {0}", userName).ConfigureAwait (false);
message = await SendCommandAsync (doAsync, cancellationToken, encoding, "PASS {0}", password).ConfigureAwait (false);
} catch (Pop3CommandException) {
throw new AuthenticationException ();
}
engine.State = Pop3EngineState.Transaction;
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
await UpdateMessageCountAsync (doAsync, cancellationToken).ConfigureAwait (false);
await ProbeCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
OnAuthenticated (message);
}
/// <summary>
/// Authenticate using the supplied credentials.
/// </summary>
/// <remarks>
/// <para>If the POP3 server supports the APOP authentication mechanism,
/// then APOP is used.</para>
/// <para>If the APOP authentication mechanism is not supported and the
/// server supports one or more SASL authentication mechanisms, then
/// the SASL mechanisms that both the client and server support are tried
/// in order of greatest security to weakest security. Once a SASL
/// authentication mechanism is found that both client and server support,
/// the credentials are used to authenticate.</para>
/// <para>If the server does not support SASL or if no common SASL mechanisms
/// can be found, then the <c>USER</c> and <c>PASS</c> commands are used as a
/// fallback.</para>
/// <note type="tip"><para>To prevent the usage of certain authentication mechanisms,
/// simply remove them from the <see cref="AuthenticationMechanisms"/> hash set
/// before calling this method.</para>
/// <para>In the case of the APOP authentication mechanism, remove it from the
/// <see cref="Capabilities"/> property instead.</para></note>
/// </remarks>
/// <param name="encoding">The text encoding to use for the user's credentials.</param>
/// <param name="credentials">The user's credentials.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="encoding"/> is <c>null</c>.</para>
/// <para>-or-</para>
/// <para><paramref name="credentials"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// The <see cref="Pop3Client"/> is already authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="MailKit.Security.AuthenticationException">
/// Authentication using the supplied credentials has failed.
/// </exception>
/// <exception cref="MailKit.Security.SaslException">
/// A SASL authentication error occurred.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// A POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// An POP3 protocol error occurred.
/// </exception>
public override void Authenticate (Encoding encoding, ICredentials credentials, CancellationToken cancellationToken = default (CancellationToken))
{
AuthenticateAsync (encoding, credentials, false, cancellationToken).GetAwaiter ().GetResult ();
}
internal void ReplayConnect (string host, Stream replayStream, CancellationToken cancellationToken = default (CancellationToken))
{
if (host == null)
throw new ArgumentNullException (nameof (host));
if (replayStream == null)
throw new ArgumentNullException (nameof (replayStream));
CheckDisposed ();
probed = ProbedCapabilities.None;
secure = false;
engine.Uri = new Uri ($"pop://{host}:110");
engine.Connect (new Pop3Stream (replayStream, ProtocolLogger), cancellationToken);
engine.QueryCapabilities (cancellationToken);
engine.Disconnected += OnEngineDisconnected;
OnConnected (host, 110, SecureSocketOptions.None);
}
internal async Task ReplayConnectAsync (string host, Stream replayStream, CancellationToken cancellationToken = default (CancellationToken))
{
if (host == null)
throw new ArgumentNullException (nameof (host));
if (replayStream == null)
throw new ArgumentNullException (nameof (replayStream));
CheckDisposed ();
probed = ProbedCapabilities.None;
secure = false;
engine.Uri = new Uri ($"pop://{host}:110");
await engine.ConnectAsync (new Pop3Stream (replayStream, ProtocolLogger), cancellationToken).ConfigureAwait (false);
await engine.QueryCapabilitiesAsync (cancellationToken).ConfigureAwait (false);
engine.Disconnected += OnEngineDisconnected;
OnConnected (host, 110, SecureSocketOptions.None);
}
internal static void ComputeDefaultValues (string host, ref int port, ref SecureSocketOptions options, out Uri uri, out bool starttls)
{
switch (options) {
default:
if (port == 0)
port = 110;
break;
case SecureSocketOptions.Auto:
switch (port) {
case 0: port = 110; goto default;
case 995: options = SecureSocketOptions.SslOnConnect; break;
default: options = SecureSocketOptions.StartTlsWhenAvailable; break;
}
break;
case SecureSocketOptions.SslOnConnect:
if (port == 0)
port = 995;
break;
}
switch (options) {
case SecureSocketOptions.StartTlsWhenAvailable:
uri = new Uri (string.Format (CultureInfo.InvariantCulture, "pop://{0}:{1}/?starttls=when-available", host, port));
starttls = true;
break;
case SecureSocketOptions.StartTls:
uri = new Uri (string.Format (CultureInfo.InvariantCulture, "pop://{0}:{1}/?starttls=always", host, port));
starttls = true;
break;
case SecureSocketOptions.SslOnConnect:
uri = new Uri (string.Format (CultureInfo.InvariantCulture, "pops://{0}:{1}", host, port));
starttls = false;
break;
default:
uri = new Uri (string.Format (CultureInfo.InvariantCulture, "pop://{0}:{1}", host, port));
starttls = false;
break;
}
}
async Task ConnectAsync (string host, int port, SecureSocketOptions options, bool doAsync, CancellationToken cancellationToken)
{
if (host == null)
throw new ArgumentNullException (nameof (host));
if (host.Length == 0)
throw new ArgumentException ("The host name cannot be empty.", nameof (host));
if (port < 0 || port > 65535)
throw new ArgumentOutOfRangeException (nameof (port));
CheckDisposed ();
if (IsConnected)
throw new InvalidOperationException ("The Pop3Client is already connected.");
Stream stream;
bool starttls;
Uri uri;
ComputeDefaultValues (host, ref port, ref options, out uri, out starttls);
var socket = await ConnectSocket (host, port, doAsync, cancellationToken).ConfigureAwait (false);
engine.Uri = uri;
if (options == SecureSocketOptions.SslOnConnect) {
var ssl = new SslStream (new NetworkStream (socket, true), false, ValidateRemoteCertificate);
try {
if (doAsync) {
await ssl.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).ConfigureAwait (false);
} else {
#if NETSTANDARD1_3 || NETSTANDARD1_6
ssl.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).GetAwaiter ().GetResult ();
#else
ssl.AuthenticateAsClient (host, ClientCertificates, SslProtocols, CheckCertificateRevocation);
#endif
}
} catch (Exception ex) {
ssl.Dispose ();
throw SslHandshakeException.Create (this, ex, false);
}
secure = true;
stream = ssl;
} else {
stream = new NetworkStream (socket, true);
secure = false;
}
probed = ProbedCapabilities.None;
if (stream.CanTimeout) {
stream.WriteTimeout = timeout;
stream.ReadTimeout = timeout;
}
try {
ProtocolLogger.LogConnect (uri);
} catch {
stream.Dispose ();
secure = false;
throw;
}
var pop3 = new Pop3Stream (stream, ProtocolLogger);
if (doAsync)
await engine.ConnectAsync (pop3, cancellationToken).ConfigureAwait (false);
else
engine.Connect (pop3, cancellationToken);
try {
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
if (options == SecureSocketOptions.StartTls && (engine.Capabilities & Pop3Capabilities.StartTLS) == 0)
throw new NotSupportedException ("The POP3 server does not support the STLS extension.");
if (starttls && (engine.Capabilities & Pop3Capabilities.StartTLS) != 0) {
await SendCommandAsync (doAsync, cancellationToken, "STLS").ConfigureAwait (false);
try {
var tls = new SslStream (stream, false, ValidateRemoteCertificate);
engine.Stream.Stream = tls;
if (doAsync) {
await tls.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).ConfigureAwait (false);
} else {
#if NETSTANDARD1_3 || NETSTANDARD1_6
tls.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).GetAwaiter ().GetResult ();
#else
tls.AuthenticateAsClient (host, ClientCertificates, SslProtocols, CheckCertificateRevocation);
#endif
}
} catch (Exception ex) {
throw SslHandshakeException.Create (this, ex, true);
}
secure = true;
// re-issue a CAPA command
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
}
} catch {
engine.Disconnect ();
secure = false;
throw;
}
engine.Disconnected += OnEngineDisconnected;
OnConnected (host, port, options);
}
/// <summary>
/// Establish a connection to the specified POP3 or POP3/S server.
/// </summary>
/// <remarks>
/// <para>Establishes a connection to the specified POP3 or POP3/S server.</para>
/// <para>If the <paramref name="port"/> has a value of <c>0</c>, then the
/// <paramref name="options"/> parameter is used to determine the default port to
/// connect to. The default port used with <see cref="SecureSocketOptions.SslOnConnect"/>
/// is <c>995</c>. All other values will use a default port of <c>110</c>.</para>
/// <para>If the <paramref name="options"/> has a value of
/// <see cref="SecureSocketOptions.Auto"/>, then the <paramref name="port"/> is used
/// to determine the default security options. If the <paramref name="port"/> has a value
/// of <c>995</c>, then the default options used will be
/// <see cref="SecureSocketOptions.SslOnConnect"/>. All other values will use
/// <see cref="SecureSocketOptions.StartTlsWhenAvailable"/>.</para>
/// <para>Once a connection is established, properties such as
/// <see cref="AuthenticationMechanisms"/> and <see cref="Capabilities"/> will be
/// populated.</para>
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="DownloadMessages"/>
/// </example>
/// <param name="host">The host name to connect to.</param>
/// <param name="port">The port to connect to. If the specified port is <c>0</c>, then the default port will be used.</param>
/// <param name="options">The secure socket options to when connecting.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="host"/> is <c>null</c>.
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="port"/> is not between <c>0</c> and <c>65535</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// The <paramref name="host"/> is a zero-length string.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// The <see cref="Pop3Client"/> is already connected.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// <paramref name="options"/> was set to
/// <see cref="MailKit.Security.SecureSocketOptions.StartTls"/>
/// and the POP3 server does not support the STLS extension.
/// </exception>
/// <exception cref="System.Net.Sockets.SocketException">
/// A socket error occurred trying to connect to the remote host.
/// </exception>
/// <exception cref="SslHandshakeException">
/// An error occurred during the SSL/TLS negotiations.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// A POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void Connect (string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken))
{
ConnectAsync (host, port, options, false, cancellationToken).GetAwaiter ().GetResult ();
}
async Task ConnectAsync (Stream stream, string host, int port, SecureSocketOptions options, bool doAsync, CancellationToken cancellationToken)
{
if (stream == null)
throw new ArgumentNullException (nameof (stream));
if (host == null)
throw new ArgumentNullException (nameof (host));
if (host.Length == 0)
throw new ArgumentException ("The host name cannot be empty.", nameof (host));
if (port < 0 || port > 65535)
throw new ArgumentOutOfRangeException (nameof (port));
CheckDisposed ();
if (IsConnected)
throw new InvalidOperationException ("The Pop3Client is already connected.");
Stream network;
bool starttls;
Uri uri;
ComputeDefaultValues (host, ref port, ref options, out uri, out starttls);
engine.Uri = uri;
if (options == SecureSocketOptions.SslOnConnect) {
var ssl = new SslStream (stream, false, ValidateRemoteCertificate);
try {
if (doAsync) {
await ssl.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).ConfigureAwait (false);
} else {
#if NETSTANDARD1_3 || NETSTANDARD1_6
ssl.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).GetAwaiter ().GetResult ();
#else
ssl.AuthenticateAsClient (host, ClientCertificates, SslProtocols, CheckCertificateRevocation);
#endif
}
} catch (Exception ex) {
ssl.Dispose ();
throw SslHandshakeException.Create (this, ex, false);
}
network = ssl;
secure = true;
} else {
network = stream;
secure = false;
}
probed = ProbedCapabilities.None;
if (network.CanTimeout) {
network.WriteTimeout = timeout;
network.ReadTimeout = timeout;
}
try {
ProtocolLogger.LogConnect (uri);
} catch {
network.Dispose ();
secure = false;
throw;
}
var pop3 = new Pop3Stream (network, ProtocolLogger);
if (doAsync)
await engine.ConnectAsync (pop3, cancellationToken).ConfigureAwait (false);
else
engine.Connect (pop3, cancellationToken);
try {
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
if (options == SecureSocketOptions.StartTls && (engine.Capabilities & Pop3Capabilities.StartTLS) == 0)
throw new NotSupportedException ("The POP3 server does not support the STLS extension.");
if (starttls && (engine.Capabilities & Pop3Capabilities.StartTLS) != 0) {
await SendCommandAsync (doAsync, cancellationToken, "STLS").ConfigureAwait (false);
var tls = new SslStream (network, false, ValidateRemoteCertificate);
engine.Stream.Stream = tls;
try {
if (doAsync) {
await tls.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).ConfigureAwait (false);
} else {
#if NETSTANDARD1_3 || NETSTANDARD1_6
tls.AuthenticateAsClientAsync (host, ClientCertificates, SslProtocols, CheckCertificateRevocation).GetAwaiter ().GetResult ();
#else
tls.AuthenticateAsClient (host, ClientCertificates, SslProtocols, CheckCertificateRevocation);
#endif
}
} catch (Exception ex) {
throw SslHandshakeException.Create (this, ex, true);
}
secure = true;
// re-issue a CAPA command
await QueryCapabilitiesAsync (doAsync, cancellationToken).ConfigureAwait (false);
}
} catch {
engine.Disconnect ();
secure = false;
throw;
}
engine.Disconnected += OnEngineDisconnected;
OnConnected (host, port, options);
}
Task ConnectAsync (Socket socket, string host, int port, SecureSocketOptions options, bool doAsync, CancellationToken cancellationToken)
{
if (socket == null)
throw new ArgumentNullException (nameof (socket));
if (!socket.Connected)
throw new ArgumentException ("The socket is not connected.", nameof (socket));
return ConnectAsync (new NetworkStream (socket, true), host, port, options, doAsync, cancellationToken);
}
/// <summary>
/// Establish a connection to the specified POP3 or POP3/S server using the provided socket.
/// </summary>
/// <remarks>
/// <para>Establishes a connection to the specified POP3 or POP3/S server using
/// the provided socket.</para>
/// <para>If the <paramref name="options"/> has a value of
/// <see cref="SecureSocketOptions.Auto"/>, then the <paramref name="port"/> is used
/// to determine the default security options. If the <paramref name="port"/> has a value
/// of <c>995</c>, then the default options used will be
/// <see cref="SecureSocketOptions.SslOnConnect"/>. All other values will use
/// <see cref="SecureSocketOptions.StartTlsWhenAvailable"/>.</para>
/// <para>Once a connection is established, properties such as
/// <see cref="AuthenticationMechanisms"/> and <see cref="Capabilities"/> will be
/// populated.</para>
/// <note type="info">With the exception of using the <paramref name="port"/> to determine the
/// default <see cref="SecureSocketOptions"/> to use when the <paramref name="options"/> value
/// is <see cref="SecureSocketOptions.Auto"/>, the <paramref name="host"/> and
/// <paramref name="port"/> parameters are only used for logging purposes.</note>
/// </remarks>
/// <param name="socket">The socket to use for the connection.</param>
/// <param name="host">The host name to connect to.</param>
/// <param name="port">The port to connect to. If the specified port is <c>0</c>, then the default port will be used.</param>
/// <param name="options">The secure socket options to when connecting.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="socket"/> is <c>null</c>.</para>
/// <para>-or-</para>
/// <para><paramref name="host"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="port"/> is not between <c>0</c> and <c>65535</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// <para><paramref name="socket"/> is not connected.</para>
/// <para>-or-</para>
/// <para>The <paramref name="host"/> is a zero-length string.</para>
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// The <see cref="Pop3Client"/> is already connected.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// <paramref name="options"/> was set to
/// <see cref="MailKit.Security.SecureSocketOptions.StartTls"/>
/// and the POP3 server does not support the STLS extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="SslHandshakeException">
/// An error occurred during the SSL/TLS negotiations.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// A POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void Connect (Socket socket, string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken))
{
ConnectAsync (socket, host, port, options, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Establish a connection to the specified POP3 or POP3/S server using the provided stream.
/// </summary>
/// <remarks>
/// <para>Establishes a connection to the specified POP3 or POP3/S server using
/// the provided stream.</para>
/// <para>If the <paramref name="options"/> has a value of
/// <see cref="SecureSocketOptions.Auto"/>, then the <paramref name="port"/> is used
/// to determine the default security options. If the <paramref name="port"/> has a value
/// of <c>995</c>, then the default options used will be
/// <see cref="SecureSocketOptions.SslOnConnect"/>. All other values will use
/// <see cref="SecureSocketOptions.StartTlsWhenAvailable"/>.</para>
/// <para>Once a connection is established, properties such as
/// <see cref="AuthenticationMechanisms"/> and <see cref="Capabilities"/> will be
/// populated.</para>
/// <note type="info">With the exception of using the <paramref name="port"/> to determine the
/// default <see cref="SecureSocketOptions"/> to use when the <paramref name="options"/> value
/// is <see cref="SecureSocketOptions.Auto"/>, the <paramref name="host"/> and
/// <paramref name="port"/> parameters are only used for logging purposes.</note>
/// </remarks>
/// <param name="stream">The stream to use for the connection.</param>
/// <param name="host">The host name to connect to.</param>
/// <param name="port">The port to connect to. If the specified port is <c>0</c>, then the default port will be used.</param>
/// <param name="options">The secure socket options to when connecting.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <para><paramref name="stream"/> is <c>null</c>.</para>
/// <para>-or-</para>
/// <para><paramref name="host"/> is <c>null</c>.</para>
/// </exception>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="port"/> is not between <c>0</c> and <c>65535</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// The <paramref name="host"/> is a zero-length string.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// The <see cref="Pop3Client"/> is already connected.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// <paramref name="options"/> was set to
/// <see cref="MailKit.Security.SecureSocketOptions.StartTls"/>
/// and the POP3 server does not support the STLS extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="SslHandshakeException">
/// An error occurred during the SSL/TLS negotiations.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// A POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void Connect (Stream stream, string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto, CancellationToken cancellationToken = default (CancellationToken))
{
ConnectAsync (stream, host, port, options, false, cancellationToken).GetAwaiter ().GetResult ();
}
async Task DisconnectAsync (bool quit, bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
if (!engine.IsConnected)
return;
if (quit) {
try {
await SendCommandAsync (doAsync, cancellationToken, "QUIT").ConfigureAwait (false);
} catch (OperationCanceledException) {
} catch (Pop3ProtocolException) {
} catch (Pop3CommandException) {
} catch (IOException) {
}
}
disconnecting = true;
engine.Disconnect ();
}
/// <summary>
/// Disconnect the service.
/// </summary>
/// <remarks>
/// If <paramref name="quit"/> is <c>true</c>, a <c>QUIT</c> command will be issued in order to disconnect cleanly.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="DownloadMessages"/>
/// </example>
/// <param name="quit">If set to <c>true</c>, a <c>QUIT</c> command will be issued in order to disconnect cleanly.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
public override void Disconnect (bool quit, CancellationToken cancellationToken = default (CancellationToken))
{
DisconnectAsync (quit, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the message count.
/// </summary>
/// <remarks>
/// Gets the message count.
/// </remarks>
/// <returns>The message count.</returns>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override int GetMessageCount (CancellationToken cancellationToken = default (CancellationToken))
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
UpdateMessageCountAsync (false, cancellationToken).GetAwaiter ().GetResult ();
return Count;
}
Task NoOpAsync (bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
return SendCommandAsync (doAsync, cancellationToken, "NOOP");
}
/// <summary>
/// Ping the POP3 server to keep the connection alive.
/// </summary>
/// <remarks>Mail servers, if left idle for too long, will automatically drop the connection.</remarks>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void NoOp (CancellationToken cancellationToken = default (CancellationToken))
{
NoOpAsync (false, cancellationToken).GetAwaiter ().GetResult ();
}
void OnEngineDisconnected (object sender, EventArgs e)
{
var options = SecureSocketOptions.None;
bool requested = disconnecting;
string host = null;
int port = 0;
if (engine.Uri != null) {
options = GetSecureSocketOptions (engine.Uri);
host = engine.Uri.Host;
port = engine.Uri.Port;
}
engine.Disconnected -= OnEngineDisconnected;
disconnecting = secure = utf8 = false;
octets = total = 0;
engine.Uri = null;
if (host != null)
OnDisconnected (host, port, options, requested);
}
#endregion
async Task EnableUTF8Async (bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
if (engine.State != Pop3EngineState.Connected)
throw new InvalidOperationException ("You must enable UTF-8 mode before authenticating.");
if ((engine.Capabilities & Pop3Capabilities.UTF8) == 0)
throw new NotSupportedException ("The POP3 server does not support the UTF8 extension.");
if (utf8)
return;
await SendCommandAsync (doAsync, cancellationToken, "UTF8").ConfigureAwait (false);
utf8 = true;
}
/// <summary>
/// Enable UTF8 mode.
/// </summary>
/// <remarks>
/// The POP3 UTF8 extension allows the client to retrieve messages in the UTF-8 encoding and
/// may also allow the user to authenticate using a UTF-8 encoded username or password.
/// </remarks>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// The <see cref="Pop3Client"/> has already been authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UTF8 extension.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public void EnableUTF8 (CancellationToken cancellationToken = default (CancellationToken))
{
EnableUTF8Async (false, cancellationToken).GetAwaiter ().GetResult ();
}
async Task<IList<Pop3Language>> GetLanguagesAsync (bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
if ((Capabilities & Pop3Capabilities.Lang) == 0)
throw new NotSupportedException ("The POP3 server does not support the LANG extension.");
var langs = new List<Pop3Language> ();
var pc = engine.QueueCommand (cancellationToken, async (pop3, cmd, text, xdoAsync) => {
if (cmd.Status != Pop3CommandStatus.Ok)
return;
do {
string response;
if (xdoAsync)
response = await engine.ReadLineAsync (cmd.CancellationToken).ConfigureAwait (false);
else
response = engine.ReadLine (cmd.CancellationToken);
if (response == ".")
break;
var tokens = response.Split (new [] { ' ' }, 2);
if (tokens.Length != 2)
continue;
langs.Add (new Pop3Language (tokens[0], tokens[1]));
} while (true);
}, "LANG");
int id;
do {
if (doAsync)
id = await engine.IterateAsync ().ConfigureAwait (false);
else
id = engine.Iterate ();
} while (id < pc.Id);
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
if (pc.Exception != null)
throw pc.Exception;
return new ReadOnlyCollection<Pop3Language> (langs);
}
/// <summary>
/// Get the list of languages supported by the POP3 server.
/// </summary>
/// <remarks>
/// If the POP3 server supports the LANG extension, it is possible to
/// query the list of languages supported by the POP3 server that can
/// be used for error messages.
/// </remarks>
/// <returns>The supported languages.</returns>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the LANG extension.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public IList<Pop3Language> GetLanguages (CancellationToken cancellationToken = default (CancellationToken))
{
return GetLanguagesAsync (false, cancellationToken).GetAwaiter ().GetResult ();
}
Task SetLanguageAsync (string lang, bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
if (lang == null)
throw new ArgumentNullException (nameof (lang));
if (lang.Length == 0)
throw new ArgumentException ("The language code cannot be empty.", nameof (lang));
if ((Capabilities & Pop3Capabilities.Lang) == 0)
throw new NotSupportedException ("The POP3 server does not support the LANG extension.");
return SendCommandAsync (doAsync, cancellationToken, "LANG {0}", lang);
}
/// <summary>
/// Set the language used by the POP3 server for error messages.
/// </summary>
/// <remarks>
/// If the POP3 server supports the LANG extension, it is possible to
/// set the language used by the POP3 server for error messages.
/// </remarks>
/// <param name="lang">The language code.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="lang"/> is <c>null</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// <paramref name="lang"/> is empty.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the LANG extension.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public void SetLanguage (string lang, CancellationToken cancellationToken = default (CancellationToken))
{
SetLanguageAsync (lang, false, cancellationToken).GetAwaiter ().GetResult ();
}
#region IMailSpool implementation
/// <summary>
/// Get the number of messages available in the message spool.
/// </summary>
/// <remarks>
/// <para>Gets the number of messages available on the POP3 server.</para>
/// <para>Once authenticated, the <see cref="Count"/> property will be set
/// to the number of available messages on the POP3 server.</para>
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="DownloadMessages"/>
/// </example>
/// <value>The message count.</value>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
public override int Count {
get {
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
return total;
}
}
/// <summary>
/// Gets whether or not the <see cref="Pop3Client"/> supports referencing messages by UIDs.
/// </summary>
/// <remarks>
/// <para>Not all servers support referencing messages by UID, so this property should
/// be checked before using <see cref="GetMessageUid(int, CancellationToken)"/>
/// and <see cref="GetMessageUids(CancellationToken)"/>.</para>
/// <para>If the server does not support UIDs, then all methods that take UID arguments
/// along with <see cref="GetMessageUid(int, CancellationToken)"/> and
/// <see cref="GetMessageUids(CancellationToken)"/> will fail.</para>
/// </remarks>
/// <value><c>true</c> if supports UIDs; otherwise, <c>false</c>.</value>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
public override bool SupportsUids {
get {
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
return (engine.Capabilities & Pop3Capabilities.UIDL) != 0;
}
}
class MessageUidContext
{
readonly Pop3Client client;
readonly int seqid;
string uid;
public MessageUidContext (Pop3Client client, int seqid)
{
this.client = client;
this.seqid = seqid;
}
Pop3Engine Engine {
get { return client.engine; }
}
Task OnDataReceived (Pop3Engine pop3, Pop3Command pc, string text, bool doAsync)
{
if (pc.Status != Pop3CommandStatus.Ok)
return Task.FromResult (true);
var tokens = text.Split (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int id;
if (tokens.Length < 2) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an incomplete response to the UIDL command.");
return Task.FromResult (true);
}
if (!int.TryParse (tokens[0], NumberStyles.None, CultureInfo.InvariantCulture, out id) || id != seqid) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an unexpected response to the UIDL command.");
return Task.FromResult (true);
}
uid = tokens[1];
return Task.FromResult (true);
}
public async Task<string> GetUidAsync (bool doAsync, CancellationToken cancellationToken)
{
var pc = Engine.QueueCommand (cancellationToken, OnDataReceived, "UIDL {0}", seqid.ToString (CultureInfo.InvariantCulture));
int id;
do {
if (doAsync)
id = await Engine.IterateAsync ().ConfigureAwait (false);
else
id = Engine.Iterate ();
} while (id < pc.Id);
client.probed |= ProbedCapabilities.UIDL;
if (pc.Status != Pop3CommandStatus.Ok) {
if (!client.SupportsUids)
throw new NotSupportedException ("The POP3 server does not support the UIDL extension.");
throw CreatePop3Exception (pc);
}
if (pc.Exception != null)
throw pc.Exception;
Engine.Capabilities |= Pop3Capabilities.UIDL;
return uid;
}
}
Task<string> GetMessageUidAsync (int index, bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (index < 0 || index >= total)
throw new ArgumentOutOfRangeException (nameof (index));
if (!SupportsUids && (probed & ProbedCapabilities.UIDL) != 0)
throw new NotSupportedException ("The POP3 server does not support the UIDL extension.");
var ctx = new MessageUidContext (this, index + 1);
return ctx.GetUidAsync (doAsync, cancellationToken);
}
/// <summary>
/// Get the UID of the message at the specified index.
/// </summary>
/// <remarks>
/// <para>Gets the UID of the message at the specified index.</para>
/// <note type="warning">Not all servers support UIDs, so you should first check the
/// <see cref="Capabilities"/> property for the <see cref="Pop3Capabilities.UIDL"/> flag or
/// the <see cref="SupportsUids"/> convenience property.</note>
/// </remarks>
/// <returns>The message UID.</returns>
/// <param name="index">The message index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="index"/> is not a valid message index.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override string GetMessageUid (int index, CancellationToken cancellationToken = default (CancellationToken))
{
return GetMessageUidAsync (index, false, cancellationToken).GetAwaiter ().GetResult ();
}
class MessageUidsContext
{
readonly Pop3Client client;
readonly List<string> uids;
public MessageUidsContext (Pop3Client client)
{
uids = new List<string> ();
this.client = client;
}
Pop3Engine Engine {
get { return client.engine; }
}
async Task OnDataReceived (Pop3Engine pop3, Pop3Command pc, string text, bool doAsync)
{
if (pc.Status != Pop3CommandStatus.Ok)
return;
do {
string response;
if (doAsync)
response = await Engine.ReadLineAsync (pc.CancellationToken).ConfigureAwait (false);
else
response = Engine.ReadLine (pc.CancellationToken);
if (response == ".")
break;
if (pc.Exception != null)
continue;
var tokens = response.Split (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int seqid;
if (tokens.Length < 2) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an incomplete response to the UIDL command.");
continue;
}
if (!int.TryParse (tokens[0], NumberStyles.None, CultureInfo.InvariantCulture, out seqid) || seqid != uids.Count + 1) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an invalid response to the UIDL command.");
continue;
}
uids.Add (tokens[1]);
} while (true);
}
public async Task<IList<string>> GetUidsAsync (bool doAsync, CancellationToken cancellationToken)
{
var pc = Engine.QueueCommand (cancellationToken, OnDataReceived, "UIDL");
int id;
do {
if (doAsync)
id = await Engine.IterateAsync ().ConfigureAwait (false);
else
id = Engine.Iterate ();
} while (id < pc.Id);
client.probed |= ProbedCapabilities.UIDL;
if (pc.Status != Pop3CommandStatus.Ok) {
if (!client.SupportsUids)
throw new NotSupportedException ("The POP3 server does not support the UIDL extension.");
throw CreatePop3Exception (pc);
}
if (pc.Exception != null)
throw pc.Exception;
Engine.Capabilities |= Pop3Capabilities.UIDL;
return uids;
}
}
Task<IList<string>> GetMessageUidsAsync (bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (!SupportsUids && (probed & ProbedCapabilities.UIDL) != 0)
throw new NotSupportedException ("The POP3 server does not support the UIDL extension.");
var ctx = new MessageUidsContext (this);
return ctx.GetUidsAsync (doAsync, cancellationToken);
}
/// <summary>
/// Get the full list of available message UIDs.
/// </summary>
/// <remarks>
/// <para>Gets the full list of available message UIDs.</para>
/// <note type="warning">Not all servers support UIDs, so you should first check the
/// <see cref="Capabilities"/> property for the <see cref="Pop3Capabilities.UIDL"/> flag or
/// the <see cref="SupportsUids"/> convenience property.</note>
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="DownloadNewMessages"/>
/// </example>
/// <returns>The message uids.</returns>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<string> GetMessageUids (CancellationToken cancellationToken = default (CancellationToken))
{
return GetMessageUidsAsync (false, cancellationToken).GetAwaiter ().GetResult ();
}
class MessageSizeContext
{
readonly Pop3Client client;
readonly int seqid;
int size;
public MessageSizeContext (Pop3Client client, int seqid)
{
this.client = client;
this.seqid = seqid;
}
Pop3Engine Engine {
get { return client.engine; }
}
Task OnDataReceived (Pop3Engine pop3, Pop3Command pc, string text, bool doAsync)
{
if (pc.Status != Pop3CommandStatus.Ok)
return Task.FromResult (true);
var tokens = text.Split (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int id;
if (tokens.Length < 2) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an incomplete response to the LIST command: {0}", text);
return Task.FromResult (true);
}
if (!int.TryParse (tokens[0], NumberStyles.None, CultureInfo.InvariantCulture, out id) || id != seqid) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an unexpected sequence-id token to the LIST command: {0}", tokens[0]);
return Task.FromResult (true);
}
if (!int.TryParse (tokens[1], NumberStyles.None, CultureInfo.InvariantCulture, out size) || size < 0) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an unexpected size token to the LIST command: {0}", tokens[1]);
return Task.FromResult (true);
}
return Task.FromResult (true);
}
public async Task<int> GetSizeAsync (bool doAsync, CancellationToken cancellationToken)
{
var pc = Engine.QueueCommand (cancellationToken, OnDataReceived, "LIST {0}", seqid.ToString (CultureInfo.InvariantCulture));
int id;
do {
if (doAsync)
id = await Engine.IterateAsync ().ConfigureAwait (false);
else
id = Engine.Iterate ();
} while (id < pc.Id);
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
if (pc.Exception != null)
throw pc.Exception;
return size;
}
}
Task<int> GetMessageSizeAsync (int index, bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (index < 0 || index >= total)
throw new ArgumentOutOfRangeException (nameof (index));
var ctx = new MessageSizeContext (this, index + 1);
return ctx.GetSizeAsync (doAsync, cancellationToken);
}
/// <summary>
/// Get the size of the specified message, in bytes.
/// </summary>
/// <remarks>
/// Gets the size of the specified message, in bytes.
/// </remarks>
/// <returns>The message size, in bytes.</returns>
/// <param name="index">The index of the message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="index"/> is not a valid message index.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override int GetMessageSize (int index, CancellationToken cancellationToken = default (CancellationToken))
{
return GetMessageSizeAsync (index, false, cancellationToken).GetAwaiter ().GetResult ();
}
class MessageSizesContext
{
readonly Pop3Client client;
readonly List<int> sizes;
public MessageSizesContext (Pop3Client client)
{
sizes = new List<int> ();
this.client = client;
}
Pop3Engine Engine {
get { return client.engine; }
}
async Task OnDataReceived (Pop3Engine pop3, Pop3Command pc, string text, bool doAsync)
{
if (pc.Status != Pop3CommandStatus.Ok)
return;
do {
string response;
if (doAsync)
response = await Engine.ReadLineAsync (pc.CancellationToken).ConfigureAwait (false);
else
response = Engine.ReadLine (pc.CancellationToken);
if (response == ".")
break;
if (pc.Exception != null)
continue;
var tokens = response.Split (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int seqid, size;
if (tokens.Length < 2) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an incomplete response to the LIST command: {0}", response);
continue;
}
if (!int.TryParse (tokens[0], NumberStyles.None, CultureInfo.InvariantCulture, out seqid) || seqid != sizes.Count + 1) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an unexpected sequence-id token to the LIST command: {0}", tokens[0]);
continue;
}
if (!int.TryParse (tokens[1], NumberStyles.None, CultureInfo.InvariantCulture, out size) || size < 0) {
pc.Exception = CreatePop3ParseException ("Pop3 server returned an unexpected size token to the LIST command: {0}", tokens[1]);
continue;
}
sizes.Add (size);
} while (true);
}
public async Task<IList<int>> GetSizesAsync (bool doAsync, CancellationToken cancellationToken)
{
var pc = Engine.QueueCommand (cancellationToken, OnDataReceived, "LIST");
int id;
do {
if (doAsync)
id = await Engine.IterateAsync ().ConfigureAwait (false);
else
id = Engine.Iterate ();
} while (id < pc.Id);
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
if (pc.Exception != null)
throw pc.Exception;
return sizes;
}
}
Task<IList<int>> GetMessageSizesAsync (bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
var ctx = new MessageSizesContext (this);
return ctx.GetSizesAsync (doAsync, cancellationToken);
}
/// <summary>
/// Get the sizes for all available messages, in bytes.
/// </summary>
/// <remarks>
/// Gets the sizes for all available messages, in bytes.
/// </remarks>
/// <returns>The message sizes, in bytes.</returns>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<int> GetMessageSizes (CancellationToken cancellationToken = default (CancellationToken))
{
return GetMessageSizesAsync (false, cancellationToken).GetAwaiter ().GetResult ();
}
abstract class DownloadContext<T>
{
readonly ITransferProgress progress;
readonly Pop3Client client;
T[] downloaded;
long nread;
int index;
protected DownloadContext (Pop3Client client, ITransferProgress progress)
{
this.progress = progress;
this.client = client;
}
protected Pop3Engine Engine {
get { return client.engine; }
}
protected abstract T Parse (Pop3Stream data, CancellationToken cancellationToken);
protected abstract Task<T> ParseAsync (Pop3Stream data, CancellationToken cancellationToken);
protected void Update (int n)
{
if (progress == null)
return;
nread += n;
progress.Report (nread);
}
async Task OnDataReceived (Pop3Engine pop3, Pop3Command pc, string text, bool doAsync)
{
if (pc.Status != Pop3CommandStatus.Ok)
return;
try {
T item;
pop3.Stream.Mode = Pop3StreamMode.Data;
if (doAsync)
item = await ParseAsync (pop3.Stream, pc.CancellationToken).ConfigureAwait (false);
else
item = Parse (pop3.Stream, pc.CancellationToken);
downloaded[index++] = item;
} catch (FormatException ex) {
pc.Exception = CreatePop3ParseException (ex, "Failed to parse data.");
if (doAsync)
await pop3.Stream.CopyToAsync (Stream.Null, 4096, pc.CancellationToken).ConfigureAwait (false);
else
pop3.Stream.CopyTo (Stream.Null, 4096);
} finally {
pop3.Stream.Mode = Pop3StreamMode.Line;
}
}
Pop3Command QueueCommand (int seqid, bool headersOnly, CancellationToken cancellationToken)
{
if (headersOnly)
return Engine.QueueCommand (cancellationToken, OnDataReceived, "TOP {0} 0", seqid.ToString (CultureInfo.InvariantCulture));
return Engine.QueueCommand (cancellationToken, OnDataReceived, "RETR {0}", seqid.ToString (CultureInfo.InvariantCulture));
}
async Task DownloadItemAsync (int seqid, bool headersOnly, bool doAsync, CancellationToken cancellationToken)
{
var pc = QueueCommand (seqid, headersOnly, cancellationToken);
int id;
do {
if (doAsync)
id = await Engine.IterateAsync ().ConfigureAwait (false);
else
id = Engine.Iterate ();
} while (id < pc.Id);
if (pc.Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (pc);
if (pc.Exception != null)
throw pc.Exception;
}
public async Task<T> DownloadAsync (int seqid, bool headersOnly, bool doAsync, CancellationToken cancellationToken)
{
downloaded = new T[1];
index = 0;
await DownloadItemAsync (seqid, headersOnly, doAsync, cancellationToken).ConfigureAwait (false);
return downloaded[0];
}
public async Task<IList<T>> DownloadAsync (IList<int> seqids, bool headersOnly, bool doAsync, CancellationToken cancellationToken)
{
downloaded = new T[seqids.Count];
index = 0;
if ((Engine.Capabilities & Pop3Capabilities.Pipelining) == 0) {
for (int i = 0; i < seqids.Count; i++)
await DownloadItemAsync (seqids[i], headersOnly, doAsync, cancellationToken);
return downloaded;
}
var commands = new Pop3Command[seqids.Count];
Pop3Command pc = null;
int id;
for (int i = 0; i < seqids.Count; i++)
commands[i] = QueueCommand (seqids[i], headersOnly, cancellationToken);
pc = commands[commands.Length - 1];
do {
if (doAsync)
id = await Engine.IterateAsync ().ConfigureAwait (false);
else
id = Engine.Iterate ();
} while (id < pc.Id);
for (int i = 0; i < commands.Length; i++) {
if (commands[i].Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (commands[i]);
if (commands[i].Exception != null)
throw commands[i].Exception;
}
return downloaded;
}
}
class DownloadStreamContext : DownloadContext<Stream>
{
public DownloadStreamContext (Pop3Client client, ITransferProgress progress = null) : base (client, progress)
{
}
protected override Stream Parse (Pop3Stream data, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested ();
var stream = new MemoryBlockStream ();
var buffer = new byte[4096];
int nread;
while ((nread = data.Read (buffer, 0, buffer.Length, cancellationToken)) > 0) {
stream.Write (buffer, 0, nread);
Update (nread);
}
stream.Position = 0;
return stream;
}
protected override async Task<Stream> ParseAsync (Pop3Stream data, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested ();
var stream = new MemoryBlockStream ();
var buffer = new byte[4096];
int nread;
while ((nread = await data.ReadAsync (buffer, 0, buffer.Length, cancellationToken).ConfigureAwait (false)) > 0) {
stream.Write (buffer, 0, nread);
Update (nread);
}
stream.Position = 0;
return stream;
}
}
class DownloadHeaderContext : DownloadContext<HeaderList>
{
readonly MimeParser parser;
public DownloadHeaderContext (Pop3Client client, MimeParser parser) : base (client, null)
{
this.parser = parser;
}
protected override HeaderList Parse (Pop3Stream data, CancellationToken cancellationToken)
{
using (var stream = new ProgressStream (data, Update)) {
parser.SetStream (ParserOptions.Default, stream);
return parser.ParseMessage (cancellationToken).Headers;
}
}
protected override async Task<HeaderList> ParseAsync (Pop3Stream data, CancellationToken cancellationToken)
{
using (var stream = new ProgressStream (data, Update)) {
parser.SetStream (ParserOptions.Default, stream);
return (await parser.ParseMessageAsync (cancellationToken).ConfigureAwait (false)).Headers;
}
}
}
class DownloadMessageContext : DownloadContext<MimeMessage>
{
readonly MimeParser parser;
public DownloadMessageContext (Pop3Client client, MimeParser parser, ITransferProgress progress = null) : base (client, progress)
{
this.parser = parser;
}
protected override MimeMessage Parse (Pop3Stream data, CancellationToken cancellationToken)
{
using (var stream = new ProgressStream (data, Update)) {
parser.SetStream (ParserOptions.Default, stream);
return parser.ParseMessage (cancellationToken);
}
}
protected override Task<MimeMessage> ParseAsync (Pop3Stream data, CancellationToken cancellationToken)
{
using (var stream = new ProgressStream (data, Update)) {
parser.SetStream (ParserOptions.Default, stream);
return parser.ParseMessageAsync (cancellationToken);
}
}
}
/// <summary>
/// Get the headers for the message at the specified index.
/// </summary>
/// <remarks>
/// Gets the headers for the message at the specified index.
/// </remarks>
/// <returns>The message headers.</returns>
/// <param name="index">The index of the message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="index"/> is not a valid message index.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override HeaderList GetMessageHeaders (int index, CancellationToken cancellationToken = default (CancellationToken))
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (index < 0 || index >= total)
throw new ArgumentOutOfRangeException (nameof (index));
var ctx = new DownloadHeaderContext (this, parser);
return ctx.DownloadAsync (index + 1, true, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the headers for the messages at the specified indexes.
/// </summary>
/// <remarks>
/// <para>Gets the headers for the messages at the specified indexes.</para>
/// <para>When the POP3 server supports the <see cref="Pop3Capabilities.Pipelining"/>
/// extension, this method will likely be more efficient than using
/// <see cref="GetMessageHeaders(int,CancellationToken)"/> for each message because
/// it will batch the commands to reduce latency.</para>
/// </remarks>
/// <returns>The headers for the specified messages.</returns>
/// <param name="indexes">The indexes of the messages.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="indexes"/> is <c>null</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// One or more of the <paramref name="indexes"/> are invalid.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<HeaderList> GetMessageHeaders (IList<int> indexes, CancellationToken cancellationToken = default (CancellationToken))
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (indexes == null)
throw new ArgumentNullException (nameof (indexes));
if (indexes.Count == 0)
return new HeaderList[0];
var seqids = new int[indexes.Count];
for (int i = 0; i < indexes.Count; i++) {
if (indexes[i] < 0 || indexes[i] >= total)
throw new ArgumentException ("One or more of the indexes are invalid.", nameof (indexes));
seqids[i] = indexes[i] + 1;
}
var ctx = new DownloadHeaderContext (this, parser);
return ctx.DownloadAsync (seqids, true, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the headers of the messages within the specified range.
/// </summary>
/// <remarks>
/// <para>Gets the headers of the messages within the specified range.</para>
/// <para>When the POP3 server supports the <see cref="Pop3Capabilities.Pipelining"/>
/// extension, this method will likely be more efficient than using
/// <see cref="GetMessageHeaders(int,CancellationToken)"/> for each message because
/// it will batch the commands to reduce latency.</para>
/// </remarks>
/// <returns>The headers of the messages within the specified range.</returns>
/// <param name="startIndex">The index of the first message to get.</param>
/// <param name="count">The number of messages to get.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="startIndex"/> and <paramref name="count"/> do not specify
/// a valid range of messages.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<HeaderList> GetMessageHeaders (int startIndex, int count, CancellationToken cancellationToken = default (CancellationToken))
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (startIndex < 0 || startIndex >= total)
throw new ArgumentOutOfRangeException (nameof (startIndex));
if (count < 0 || count > (total - startIndex))
throw new ArgumentOutOfRangeException (nameof (count));
if (count == 0)
return new HeaderList[0];
var seqids = new int[count];
for (int i = 0; i < count; i++)
seqids[i] = startIndex + i + 1;
var ctx = new DownloadHeaderContext (this, parser);
return ctx.DownloadAsync (seqids, true, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the message at the specified index.
/// </summary>
/// <remarks>
/// Gets the message at the specified index.
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="DownloadMessages"/>
/// </example>
/// <returns>The message.</returns>
/// <param name="index">The index of the message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress reporting mechanism.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="index"/> is not a valid message index.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override MimeMessage GetMessage (int index, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (index < 0 || index >= total)
throw new ArgumentOutOfRangeException (nameof (index));
var ctx = new DownloadMessageContext (this, parser, progress);
return ctx.DownloadAsync (index + 1, false, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the messages at the specified indexes.
/// </summary>
/// <remarks>
/// <para>Gets the messages at the specified indexes.</para>
/// <para>When the POP3 server supports the <see cref="Pop3Capabilities.Pipelining"/>
/// extension, this method will likely be more efficient than using
/// <see cref="GetMessage(int,CancellationToken,ITransferProgress)"/> for each message
/// because it will batch the commands to reduce latency.</para>
/// </remarks>
/// <returns>The messages.</returns>
/// <param name="indexes">The indexes of the messages.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress reporting mechanism.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="indexes"/> is <c>null</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// One or more of the <paramref name="indexes"/> are invalid.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<MimeMessage> GetMessages (IList<int> indexes, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (indexes == null)
throw new ArgumentNullException (nameof (indexes));
if (indexes.Count == 0)
return new MimeMessage[0];
var seqids = new int[indexes.Count];
for (int i = 0; i < indexes.Count; i++) {
if (indexes[i] < 0 || indexes[i] >= total)
throw new ArgumentException ("One or more of the indexes are invalid.", nameof (indexes));
seqids[i] = indexes[i] + 1;
}
var ctx = new DownloadMessageContext (this, parser, progress);
return ctx.DownloadAsync (seqids, false, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the messages within the specified range.
/// </summary>
/// <remarks>
/// <para>Gets the messages within the specified range.</para>
/// <para>When the POP3 server supports the <see cref="Pop3Capabilities.Pipelining"/>
/// extension, this method will likely be more efficient than using
/// <see cref="GetMessage(int,CancellationToken,ITransferProgress)"/> for each message
/// because it will batch the commands to reduce latency.</para>
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="BatchDownloadMessages"/>
/// </example>
/// <returns>The messages.</returns>
/// <param name="startIndex">The index of the first message to get.</param>
/// <param name="count">The number of messages to get.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress reporting mechanism.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="startIndex"/> and <paramref name="count"/> do not specify
/// a valid range of messages.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<MimeMessage> GetMessages (int startIndex, int count, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (startIndex < 0 || startIndex >= total)
throw new ArgumentOutOfRangeException (nameof (startIndex));
if (count < 0 || count > (total - startIndex))
throw new ArgumentOutOfRangeException (nameof (count));
if (count == 0)
return new MimeMessage[0];
var seqids = new int[count];
for (int i = 0; i < count; i++)
seqids[i] = startIndex + i + 1;
var ctx = new DownloadMessageContext (this, parser, progress);
return ctx.DownloadAsync (seqids, false, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the message or header stream at the specified index.
/// </summary>
/// <remarks>
/// Gets the message or header stream at the specified index.
/// </remarks>
/// <returns>The message or header stream.</returns>
/// <param name="index">The index of the message.</param>
/// <param name="headersOnly"><c>true</c> if only the headers should be retrieved; otherwise, <c>false</c>.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress reporting mechanism.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="index"/> is not a valid message index.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override Stream GetStream (int index, bool headersOnly = false, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (index < 0 || index >= total)
throw new ArgumentOutOfRangeException (nameof (index));
var ctx = new DownloadStreamContext (this, progress);
return ctx.DownloadAsync (index + 1, headersOnly, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the message or header streams at the specified indexes.
/// </summary>
/// <remarks>
/// <para>Get the message or header streams at the specified indexes.</para>
/// <para>If the POP3 server supports the <see cref="Pop3Capabilities.Pipelining"/>
/// extension, this method will likely be more efficient than using
/// <see cref="GetStream(int,bool,CancellationToken,ITransferProgress)"/> for each message
/// because it will batch the commands to reduce latency.</para>
/// </remarks>
/// <returns>The message or header streams.</returns>
/// <param name="indexes">The indexes of the messages.</param>
/// <param name="headersOnly"><c>true</c> if only the headers should be retrieved; otherwise, <c>false</c>.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress reporting mechanism.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="indexes"/> is <c>null</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// One or more of the <paramref name="indexes"/> are invalid.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<Stream> GetStreams (IList<int> indexes, bool headersOnly = false, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (indexes == null)
throw new ArgumentNullException (nameof (indexes));
if (indexes.Count == 0)
return new Stream[0];
var seqids = new int[indexes.Count];
for (int i = 0; i < indexes.Count; i++) {
if (indexes[i] < 0 || indexes[i] >= total)
throw new ArgumentException ("One or more of the indexes are invalid.", nameof (indexes));
seqids[i] = indexes[i] + 1;
}
var ctx = new DownloadStreamContext (this, progress);
return ctx.DownloadAsync (seqids, headersOnly, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Get the message or header streams within the specified range.
/// </summary>
/// <remarks>
/// <para>Gets the message or header streams within the specified range.</para>
/// <para>If the POP3 server supports the <see cref="Pop3Capabilities.Pipelining"/>
/// extension, this method will likely be more efficient than using
/// <see cref="GetStream(int,bool,CancellationToken,ITransferProgress)"/> for each message
/// because it will batch the commands to reduce latency.</para>
/// </remarks>
/// <returns>The message or header streams.</returns>
/// <param name="startIndex">The index of the first stream to get.</param>
/// <param name="count">The number of streams to get.</param>
/// <param name="headersOnly"><c>true</c> if only the headers should be retrieved; otherwise, <c>false</c>.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress reporting mechanism.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="startIndex"/> and <paramref name="count"/> do not specify
/// a valid range of messages.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.NotSupportedException">
/// The POP3 server does not support the UIDL extension.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IList<Stream> GetStreams (int startIndex, int count, bool headersOnly = false, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (startIndex < 0 || startIndex >= total)
throw new ArgumentOutOfRangeException (nameof (startIndex));
if (count < 0 || count > (total - startIndex))
throw new ArgumentOutOfRangeException (nameof (count));
if (count == 0)
return new Stream[0];
var seqids = new int[count];
for (int i = 0; i < count; i++)
seqids[i] = startIndex + i + 1;
var ctx = new DownloadStreamContext (this, progress);
return ctx.DownloadAsync (seqids, headersOnly, false, cancellationToken).GetAwaiter ().GetResult ();
}
Task DeleteMessageAsync (int index, bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (index < 0 || index >= total)
throw new ArgumentOutOfRangeException (nameof (index));
var seqid = (index + 1).ToString (CultureInfo.InvariantCulture);
return SendCommandAsync (doAsync, cancellationToken, "DELE {0}", seqid);
}
/// <summary>
/// Mark the specified message for deletion.
/// </summary>
/// <remarks>
/// Messages marked for deletion are not actually deleted until the session
/// is cleanly disconnected
/// (see <see cref="Pop3Client.Disconnect(bool, CancellationToken)"/>).
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="DownloadMessages"/>
/// </example>
/// <param name="index">The index of the message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="index"/> is not a valid message index.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void DeleteMessage (int index, CancellationToken cancellationToken = default (CancellationToken))
{
DeleteMessageAsync (index, false, cancellationToken).GetAwaiter ().GetResult ();
}
async Task DeleteMessagesAsync (IList<int> indexes, bool doAsync, CancellationToken cancellationToken = default (CancellationToken))
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (indexes == null)
throw new ArgumentNullException (nameof (indexes));
if (indexes.Count == 0)
return;
var seqids = new string[indexes.Count];
for (int i = 0; i < indexes.Count; i++) {
if (indexes[i] < 0 || indexes[i] >= total)
throw new ArgumentException ("One or more of the indexes are invalid.", nameof (indexes));
seqids[i] = (indexes[i] + 1).ToString (CultureInfo.InvariantCulture);
}
if ((Capabilities & Pop3Capabilities.Pipelining) == 0) {
for (int i = 0; i < seqids.Length; i++)
await SendCommandAsync (doAsync, cancellationToken, "DELE {0}", seqids[i]).ConfigureAwait (false);
return;
}
var commands = new Pop3Command[seqids.Length];
Pop3Command pc = null;
int id;
for (int i = 0; i < seqids.Length; i++) {
pc = engine.QueueCommand (cancellationToken, null, "DELE {0}", seqids[i]);
commands[i] = pc;
}
do {
if (doAsync)
id = await engine.IterateAsync ().ConfigureAwait (false);
else
id = engine.Iterate ();
} while (id < pc.Id);
for (int i = 0; i < commands.Length; i++) {
if (commands[i].Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (commands[i]);
}
}
/// <summary>
/// Mark the specified messages for deletion.
/// </summary>
/// <remarks>
/// Messages marked for deletion are not actually deleted until the session
/// is cleanly disconnected
/// (see <see cref="Pop3Client.Disconnect(bool, CancellationToken)"/>).
/// </remarks>
/// <param name="indexes">The indexes of the messages.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentNullException">
/// <paramref name="indexes"/> is <c>null</c>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// One or more of the <paramref name="indexes"/> are invalid.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void DeleteMessages (IList<int> indexes, CancellationToken cancellationToken = default (CancellationToken))
{
DeleteMessagesAsync (indexes, false, cancellationToken).GetAwaiter ().GetResult ();
}
async Task DeleteMessagesAsync (int startIndex, int count, bool doAsync, CancellationToken cancellationToken = default (CancellationToken))
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
if (startIndex < 0 || startIndex >= total)
throw new ArgumentOutOfRangeException (nameof (startIndex));
if (count < 0 || count > (total - startIndex))
throw new ArgumentOutOfRangeException (nameof (count));
if (count == 0)
return;
if ((Capabilities & Pop3Capabilities.Pipelining) == 0) {
for (int i = 0; i < count; i++) {
var seqid = (startIndex + i + 1).ToString (CultureInfo.InvariantCulture);
await SendCommandAsync (doAsync, cancellationToken, "DELE {0}", seqid).ConfigureAwait (false);
}
return;
}
var commands = new Pop3Command[count];
Pop3Command pc = null;
int id;
for (int i = 0; i < count; i++) {
var seqid = (startIndex + i + 1).ToString (CultureInfo.InvariantCulture);
pc = engine.QueueCommand (cancellationToken, null, "DELE {0}", seqid);
commands[i] = pc;
}
do {
if (doAsync)
id = await engine.IterateAsync ().ConfigureAwait (false);
else
id = engine.Iterate ();
} while (id < pc.Id);
for (int i = 0; i < commands.Length; i++) {
if (commands[i].Status != Pop3CommandStatus.Ok)
throw CreatePop3Exception (commands[i]);
}
}
/// <summary>
/// Mark the specified range of messages for deletion.
/// </summary>
/// <remarks>
/// Messages marked for deletion are not actually deleted until the session
/// is cleanly disconnected
/// (see <see cref="Pop3Client.Disconnect(bool, CancellationToken)"/>).
/// </remarks>
/// <example>
/// <code language="c#" source="Examples\Pop3Examples.cs" region="BatchDownloadMessages"/>
/// </example>
/// <param name="startIndex">The index of the first message to mark for deletion.</param>
/// <param name="count">The number of messages to mark for deletion.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// <paramref name="startIndex"/> and <paramref name="count"/> do not specify
/// a valid range of messages.
/// </exception>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void DeleteMessages (int startIndex, int count, CancellationToken cancellationToken = default (CancellationToken))
{
DeleteMessagesAsync (startIndex, count, false, cancellationToken).GetAwaiter ().GetResult ();
}
/// <summary>
/// Mark all messages for deletion.
/// </summary>
/// <remarks>
/// Messages marked for deletion are not actually deleted until the session
/// is cleanly disconnected
/// (see <see cref="Pop3Client.Disconnect(bool, CancellationToken)"/>).
/// </remarks>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void DeleteAllMessages (CancellationToken cancellationToken = default (CancellationToken))
{
if (total > 0)
DeleteMessages (0, total, cancellationToken);
}
Task ResetAsync (bool doAsync, CancellationToken cancellationToken)
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
return SendCommandAsync (doAsync, cancellationToken, "RSET");
}
/// <summary>
/// Reset the state of all messages marked for deletion.
/// </summary>
/// <remarks>
/// Messages marked for deletion are not actually deleted until the session
/// is cleanly disconnected
/// (see <see cref="Pop3Client.Disconnect(bool, CancellationToken)"/>).
/// </remarks>
/// <param name="cancellationToken">The cancellation token.</param>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// The POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override void Reset (CancellationToken cancellationToken = default (CancellationToken))
{
ResetAsync (false, cancellationToken).GetAwaiter ().GetResult ();
}
#endregion
#region IEnumerable<MimeMessage> implementation
/// <summary>
/// Get an enumerator for the messages in the folder.
/// </summary>
/// <remarks>
/// Gets an enumerator for the messages in the folder.
/// </remarks>
/// <returns>The enumerator.</returns>
/// <exception cref="System.ObjectDisposedException">
/// The <see cref="Pop3Client"/> has been disposed.
/// </exception>
/// <exception cref="ServiceNotConnectedException">
/// The <see cref="Pop3Client"/> is not connected.
/// </exception>
/// <exception cref="ServiceNotAuthenticatedException">
/// The <see cref="Pop3Client"/> is not authenticated.
/// </exception>
/// <exception cref="System.OperationCanceledException">
/// The operation was canceled via the cancellation token.
/// </exception>
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="Pop3CommandException">
/// A POP3 command failed.
/// </exception>
/// <exception cref="Pop3ProtocolException">
/// A POP3 protocol error occurred.
/// </exception>
public override IEnumerator<MimeMessage> GetEnumerator ()
{
CheckDisposed ();
CheckConnected ();
CheckAuthenticated ();
for (int i = 0; i < total; i++)
yield return GetMessage (i, CancellationToken.None);
yield break;
}
#endregion
/// <summary>
/// Releases the unmanaged resources used by the <see cref="Pop3Client"/> and
/// optionally releases the managed resources.
/// </summary>
/// <remarks>
/// Releases the unmanaged resources used by the <see cref="Pop3Client"/> and
/// optionally releases the managed resources.
/// </remarks>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources;
/// <c>false</c> to release only the unmanaged resources.</param>
protected override void Dispose (bool disposing)
{
if (disposing && !disposed) {
engine.Disconnect ();
disposed = true;
}
base.Dispose (disposing);
}
}
}