用POP3获取邮箱邮件内容,支持SSL验证登陆(完整C#源码)
延续上一篇用POP3获取邮箱邮件内容(C#源码)
上一篇中提到一个问题,网上一般流传的是非SSL验证登陆的,适用163之类的邮箱,但是QQ邮箱一般都是登陆不上的。这次我来提供一个支持SSL验证登陆的方法。
程序效果如下,可以看到读取的邮件内容确实是QQ邮箱的邮件。
准备工作:下载一个OpenPop
服务器pop.qq.com
端口:995
需要开启3方授权码。
下面直接上代码,程序结构如下,需要的7个class我都已经贴上来了,中间有很多功能如果用不到,可以根据自己需要删掉就行了代码运行有什么问题,直接留言给我
程序所需要的7个calss如下,namespace改成自己的就好了,别的都原版贴过去就行。
1.资源释放
using System;
namespace Sample.MailRelay
{
/// <summary>
/// Utility class that simplifies the usage of <see cref="IDisposable"/>
/// </summary>
public abstract class Disposable : IDisposable
{
/// <summary>
/// Returns <see langword="true"/> if this instance has been disposed of, <see langword="false"/> otherwise
/// </summary>
protected bool IsDisposed { get; private set; }
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="Disposable"/> is reclaimed by garbage collection.
/// </summary>
~Disposable()
{
Dispose(false);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
public void Dispose()
{
if (!IsDisposed)
{
try
{
Dispose(true);
}
finally
{
IsDisposed = true;
GC.SuppressFinalize(this);
}
}
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources. Remember to call this method from your derived classes.
/// </summary>
/// <param name="disposing">
/// Set to <c>true</c> to release both managed and unmanaged resources.<br/>
/// Set to <c>false</c> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
}
/// <summary>
/// Used to assert that the object has not been disposed
/// </summary>
/// <exception cref="ObjectDisposedException">Thrown if the object is in a disposed state.</exception>
/// <remarks>
/// The method is to be used by the subclasses in order to provide a simple method for checking the
/// disposal state of the object.
/// </remarks>
protected void AssertDisposed()
{
if (IsDisposed)
{
string typeName = GetType().FullName;
throw new ObjectDisposedException(typeName, String.Format(System.Globalization.CultureInfo.InvariantCulture, "Cannot access a disposed {0}.", typeName));
}
}
}
}
2.CRAM-MD5算法
using System;
using System.Security.Cryptography;
using System.Text;
namespace Sample.MailRelay
{
/// <summary>
/// Implements the CRAM-MD5 algorithm as specified in <a href="http://tools.ietf.org/html/rfc2195">RFC 2195</a>.
/// </summary>
internal static class CramMd5
{
/// <summary>
/// Defined by <a href="http://tools.ietf.org/html/rfc2104#section-2">RFC 2104</a>
/// Is a 64 byte array with all entries set to 0x36.
/// </summary>
private static readonly byte[] ipad;
/// <summary>
/// Defined by <a href="http://tools.ietf.org/html/rfc2104#section-2">RFC 2104</a>
/// Is a 64 byte array with all entries set to 0x5C.
/// </summary>
private static readonly byte[] opad;
/// <summary>
/// Initializes the static fields
/// </summary>
static CramMd5()
{
ipad = new byte[64];
opad = new byte[64];
for (int i = 0; i < ipad.Length; i++)
{
ipad[i] = 0x36;
opad[i] = 0x5C;
}
}
/// <summary>
/// Computes the digest needed to login to a server using CRAM-MD5.<br/>
/// <br/>
/// This computes:<br/>
/// MD5((password XOR opad), MD5((password XOR ipad), challenge))
/// </summary>
/// <param name="username">The username of the user who wants to log in</param>
/// <param name="password">The password for the <paramref name="username"/></param>
/// <param name="challenge">
/// The challenge received from the server when telling it CRAM-MD5 authenticated is wanted.
/// Is a base64 encoded string.
/// </param>
/// <returns>The response to the challenge, which the server can validate and log in the user if correct</returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="username"/>,
/// <paramref name="password"/> or
/// <paramref name="challenge"/> is <see langword="null"/>
/// </exception>
internal static string ComputeDigest(string username, string password, string challenge)
{
if (username == null)
throw new ArgumentNullException("username");
if (password == null)
throw new ArgumentNullException("password");
if (challenge == null)
throw new ArgumentNullException("challenge");
// Get the password bytes
byte[] passwordBytes = GetSharedSecretInBytes(password);
// The challenge is encoded in base64
byte[] challengeBytes = Convert.FromBase64String(challenge);
// Now XOR the password with the opad and ipad magic bytes
byte[] passwordOpad = Xor(passwordBytes, opad);
byte[] passwordIpad = Xor(passwordBytes, ipad);
// Now do the computation: MD5((password XOR opad), MD5((password XOR ipad), challenge))
byte[] digestValue = Hash(Concatenate(passwordOpad, Hash(Concatenate(passwordIpad, challengeBytes))));
// Convert the bytes to a hex string
// BitConverter writes the output as AF-B3-...
// We need lower-case output without "-"
string hex = BitConverter.ToString(digestValue).Replace("-", "").ToLowerInvariant();
// Include the username in the resulting base64 encoded response
return Convert.ToBase64String(Encoding.ASCII.GetBytes(username + " " + hex));
}
/// <summary>
/// Hashes a byte array using the MD5 algorithm.
/// </summary>
/// <param name="toHash">The byte array to hash</param>
/// <returns>The result of hashing the <paramref name="toHash"/> bytes with the MD5 algorithm</returns>
/// <exception cref="ArgumentNullException">If <paramref name="toHash"/> is <see langword="null"/></exception>
private static byte[] Hash(byte[] toHash)
{
if (toHash == null)
throw new ArgumentNullException("toHash");
using (MD5 md5 = new MD5CryptoServiceProvider())
{
return md5.ComputeHash(toHash);
}
}
/// <summary>
/// Concatenates two byte arrays into one
/// </summary>
/// <param name="one">The first byte array</param>
/// <param name="two">The second byte array</param>
/// <returns>A concatenated byte array</returns>
/// <exception cref="ArgumentNullException">If <paramref name="one"/> or <paramref name="two"/> is <see langword="null"/></exception>
private static byte[] Concatenate(byte[] one, byte[] two)
{
if (one == null)
throw new ArgumentNullException("one");
if (two == null)
throw new ArgumentNullException("two");
// Create space for both byte arrays in one
byte[] concatenated = new byte[one.Length + two.Length];
// Copy the first one over
Buffer.BlockCopy(one, 0, concatenated, 0, one.Length);
// Copy the second one over
Buffer.BlockCopy(two, 0, concatenated, one.Length, two.Length);
// Return result
return concatenated;
}
/// <summary>
/// XORs a byte array with another.<br/>
/// Each byte in <paramref name="toXor"/> is XORed with the corresponding byte
/// in <paramref name="toXorWith"/> until the end of <paramref name="toXor"/> is encountered.
/// </summary>
/// <param name="toXor">The byte array to XOR</param>
/// <param name="toXorWith">The byte array to XOR with</param>
/// <returns>A new byte array with the XORed results</returns>
/// <exception cref="ArgumentNullException">If <paramref name="toXor"/> or <paramref name="toXorWith"/> is <see langword="null"/></exception>
/// <exception cref="ArgumentException">If the lengths of the arrays are not equal</exception>
private static byte[] Xor(byte[] toXor, byte[] toXorWith)
{
if (toXor == null)
throw new ArgumentNullException("toXor");
if (toXorWith == null)
throw new ArgumentNullException("toXorWith");
if (toXor.Length != toXorWith.Length)
throw new ArgumentException("The lengths of the arrays must be equal");
// Create a new array to store results in
byte[] xored = new byte[toXor.Length];
// XOR each individual byte.
for (int i = 0; i < toXor.Length; i++)
{
xored[i] = toXor[i];
xored[i] ^= toXorWith[i];
}
// Return result
return xored;
}
/// <summary>
/// This method is responsible to generate the byte array needed
/// from the shared secret - the password.<br/>
///
/// RFC 2195 says:<br/>
/// The shared secret is null-padded to a length of 64 bytes. If the
/// shared secret is longer than 64 bytes, the MD5 digest of the
/// shared secret is used as a 16 byte input to the keyed MD5
/// calculation.
/// </summary>
/// <param name="password">This is the shared secret</param>
/// <returns>The 64 bytes that is to be used from the shared secret</returns>
/// <exception cref="ArgumentNullException">If <paramref name="password"/> is <see langword="null"/></exception>
private static byte[] GetSharedSecretInBytes(string password)
{
if (password == null)
throw new ArgumentNullException("password");
// Get the password in bytes
byte[] passwordBytes = Encoding.ASCII.GetBytes(password);
// If the length is larger than 64, we need to
if (passwordBytes.Length > 64)
{
passwordBytes = new MD5CryptoServiceProvider().ComputeHash(passwordBytes);
}
if (passwordBytes.Length != 64)
{
byte[] returner = new byte[64];
for (int i = 0; i < passwordBytes.Length; i++)
{
returner[i] = passwordBytes[i];
}
return returner;
}
return passwordBytes;
}
}
}
3.连接方式的枚举类
namespace Sample.MailRelay
{
/// <summary>
/// Some of these states are defined by <a href="http://tools.ietf.org/html/rfc1939">RFC 1939</a>.<br/>
/// Which commands that are allowed in which state can be seen in the same RFC.<br/>
/// <br/>
/// Used to keep track of which state the <see cref="Pop3Client"/> is in.
/// </summary>
internal enum ConnectionState
{
/// <summary>
/// This is when the Pop3Client is not even connected to the server
/// </summary>
Disconnected,
/// <summary>
/// This is when the server is awaiting user credentials
/// </summary>
Authorization,
/// <summary>
/// This is when the server has been given the user credentials, and we are allowed
/// to use commands specific to this users mail drop
/// </summary>
Transaction
}
}
4.登陆方式的枚举类
namespace Sample.MailRelay
{
/// <summary>
/// Describes the authentication method to use when authenticating towards a POP3 server.
/// </summary>
public enum AuthenticationMethod
{
/// <summary>
/// Authenticate using the UsernameAndPassword method.<br/>
/// This will pass the username and password to the server in cleartext.<br/>
/// <see cref="Apop"/> is more secure but might not be supported on a server.<br/>
/// This method is not recommended. Use <see cref="Auto"/> instead.
/// <br/>
/// If SSL is used, there is no loss of security by using this authentication method.
/// </summary>
UsernameAndPassword,
/// <summary>
/// Authenticate using the Authenticated Post Office Protocol method, which is more secure then
/// <see cref="UsernameAndPassword"/> since it is a request-response protocol where server checks if the
/// client knows a shared secret, which is the password, without the password itself being transmitted.<br/>
/// This authentication method uses MD5 under its hood.<br/>
/// <br/>
/// This authentication method is not supported by many servers.<br/>
/// Choose this option if you want maximum security.
/// </summary>
Apop,
/// <summary>
/// This is the recomended method to authenticate with.<br/>
/// If <see cref="Apop"/> is supported by the server, <see cref="Apop"/> is used for authentication.<br/>
/// If <see cref="Apop"/> is not supported, Auto will fall back to <see cref="UsernameAndPassword"/> authentication.
/// </summary>
Auto,
/// <summary>
/// Logs in the the POP3 server using CRAM-MD5 authentication scheme.<br/>
/// This in essence uses the MD5 hashing algorithm on the user password and a server challenge.
/// </summary>
CramMd5
}
}
5.用于计算向POP3服务器发出APOP命令时所需的摘要的类。
using System;
using System.Security.Cryptography;
using System.Text;
namespace Sample.MailRelay
{
/// <summary>
/// Class for computing the digest needed when issuing the APOP command to a POP3 server.
/// </summary>
internal static class Apop
{
/// <summary>
/// Create the digest for the APOP command so that the server can validate
/// we know the password for some user.
/// </summary>
/// <param name="password">The password for the user</param>
/// <param name="serverTimestamp">The timestamp advertised in the server greeting to the POP3 client</param>
/// <returns>The password and timestamp hashed with the MD5 algorithm outputted as a HEX string</returns>
public static string ComputeDigest(string password, string serverTimestamp)
{
if (password == null)
throw new ArgumentNullException("password");
if (serverTimestamp == null)
throw new ArgumentNullException("serverTimestamp");
// The APOP command authorizes itself by using the password together
// with the server timestamp. This way the password is not transmitted
// in clear text, and the server can still verify we have the password.
byte[] digestToHash = Encoding.ASCII.GetBytes(serverTimestamp + password);
using (MD5 md5 = new MD5CryptoServiceProvider())
{
// MD5 hash the digest
byte[] result = md5.ComputeHash(digestToHash);
// Convert the bytes to a hex string
// BitConverter writes the output as AF-B3-...
// We need lower-case output without "-"
return BitConverter.ToString(result).Replace("-", "").ToLowerInvariant();
}
}
}
}
6.邮件客户端操作方法
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using OpenPop.Mime;
using OpenPop.Mime.Header;
using OpenPop.Pop3.Exceptions;
using OpenPop.Common;
using OpenPop.Common.Logging;
namespace Sample.MailRelay
{
/// <summary>
/// POP3 compliant POP Client<br/>
/// <br/>
/// If you want to override where logging is sent, look at <see cref="DefaultLogger"/>
/// </summary>
/// <example>
/// Examples are available on the <a href="http://hpop.sourceforge.net/">project homepage</a>.
/// </example>
public class Pop3Client : Disposable
{
#region Private member properties
/// <summary>
/// The stream used to communicate with the server
/// </summary>
private Stream Stream { get; set; }
/// <summary>
/// This is the last response the server sent back when a command was issued to it
/// </summary>
private string LastServerResponse { get; set; }
/// <summary>
/// The APOP time stamp sent by the server in it's welcome message if APOP is supported.
/// </summary>
private string ApopTimeStamp { get; set; }
/// <summary>
/// Describes what state the <see cref="Pop3Client"/> is in
/// </summary>
private ConnectionState State { get; set; }
#endregion
#region Public member properties
/// <summary>
/// Tells whether the <see cref="Pop3Client"/> is connected to a POP server or not
/// </summary>
public bool Connected { get; private set; }
/// <summary>
/// Allows you to check if the server supports
/// the <see cref="AuthenticationMethod.Apop"/> authentication method.<br/>
/// <br/>
/// This value is filled when the connect method has returned,
/// as the server tells in its welcome message if APOP is supported.
/// </summary>
public bool ApopSupported { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Constructs a new Pop3Client for you to use.
/// </summary>
public Pop3Client()
{
SetInitialValues();
}
#endregion
#region IDisposable implementation
/// <summary>
/// Disposes the <see cref="Pop3Client"/>.<br/>
/// This is the implementation of the <see cref="IDisposable"/> interface.<br/>
/// Sends the QUIT command to the server before closing the streams.
/// </summary>
/// <param name="disposing"><see langword="true"/> if managed and unmanaged code should be disposed, <see langword="false"/> if only managed code should be disposed</param>
protected override void Dispose(bool disposing)
{
if (disposing && !IsDisposed)
{
if (Connected)
{
Disconnect();
}
}
base.Dispose(disposing);
}
#endregion
#region Connection managing methods
/// <summary>
/// Connect to the server using user supplied stream
/// </summary>
/// <param name="stream">The stream used to communicate with the server</param>
/// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null"/></exception>
public void Connect(Stream stream)
{
AssertDisposed();
if (State != ConnectionState.Disconnected)
throw new InvalidUseException("You cannot ask to connect to a POP3 server, when we are already connected to one. Disconnect first.");
if (stream == null)
throw new ArgumentNullException("stream");
Stream = stream;
// Fetch the server one-line welcome greeting
string response = StreamUtility.ReadLineAsAscii(Stream);
// Check if the response was an OK response
try
{
// Assume we now need the user to supply credentials
// If we do not connect correctly, Disconnect will set the
// state to Disconnected
// If this is not set, Disconnect will throw an exception
State = ConnectionState.Authorization;
IsOkResponse(response);
ExtractApopTimestamp(response);
Connected = true;
}
catch (PopServerException e)
{
// If not close down the connection and abort
DisconnectStreams();
DefaultLogger.Log.LogError("Connect(): " + "Error with connection, maybe POP3 server not exist");
DefaultLogger.Log.LogDebug("Last response from server was: " + LastServerResponse);
throw new PopServerNotAvailableException("Server is not available", e);
}
}
/// <summary>
/// Connects to a remote POP3 server using default timeouts of 60.000 milliseconds
/// </summary>
/// <param name="hostname">The <paramref name="hostname"/> of the POP3 server</param>
/// <param name="port">The port of the POP3 server</param>
/// <param name="useSsl"><see langword="true"/> if SSL should be used. <see langword="false"/> if plain TCP should be used.</param>
/// <exception cref="PopServerNotAvailableException">If the server did not send an OK message when a connection was established</exception>
/// <exception cref="PopServerNotFoundException">If it was not possible to connect to the server</exception>
/// <exception cref="ArgumentNullException">If <paramref name="hostname"/> is <see langword="null"/></exception>
/// <exception cref="ArgumentOutOfRangeException">If port is not in the range [<see cref="IPEndPoint.MinPort"/>, <see cref="IPEndPoint.MaxPort"/></exception>
public void Connect(string hostname, int port, bool useSsl)
{
const int defaultTimeOut = 600000;
Connect(hostname, port, useSsl, defaultTimeOut, defaultTimeOut, null);
}
/// <summary>
/// Connects to a remote POP3 server
/// </summary>
/// <param name="hostname">The <paramref name="hostname"/> of the POP3 server</param>
/// <param name="port">The port of the POP3 server</param>
/// <param name="useSsl"><see langword="true"/> if SSL should be used. <see langword="false"/> if plain TCP should be used.</param>
/// <param name="receiveTimeout">Timeout in milliseconds before a socket should time out from reading. Set to 0 or -1 to specify infinite timeout.</param>
/// <param name="sendTimeout">Timeout in milliseconds before a socket should time out from sending. Set to 0 or -1 to specify infinite timeout.</param>
/// <param name="certificateValidator">If you want to validate the certificate in a SSL connection, pass a reference to your validator. Supply <see langword="null"/> if default should be used.</param>
/// <exception cref="PopServerNotAvailableException">If the server did not send an OK message when a connection was established</exception>
/// <exception cref="PopServerNotFoundException">If it was not possible to connect to the server</exception>
/// <exception cref="ArgumentNullException">If <paramref name="hostname"/> is <see langword="null"/></exception>
/// <exception cref="ArgumentOutOfRangeException">If port is not in the range [<see cref="IPEndPoint.MinPort"/>, <see cref="IPEndPoint.MaxPort"/> or if any of the timeouts is less than -1.</exception>
public void Connect(string hostname, int port, bool useSsl, int receiveTimeout, int sendTimeout, RemoteCertificateValidationCallback certificateValidator)
{
AssertDisposed();
if (hostname == null)
throw new ArgumentNullException("hostname");
if (hostname.Length == 0)
throw new ArgumentException("hostname cannot be empty", "hostname");
if (port > IPEndPoint.MaxPort || port < IPEndPoint.MinPort)
throw new ArgumentOutOfRangeException("port");
if (receiveTimeout < 0)
throw new ArgumentOutOfRangeException("receiveTimeout");
if (sendTimeout < 0)
throw new ArgumentOutOfRangeException("sendTimeout");
if (State != ConnectionState.Disconnected)
throw new InvalidUseException("You cannot ask to connect to a POP3 server, when we are already connected to one. Disconnect first.");
TcpClient clientSocket = new TcpClient();
clientSocket.ReceiveTimeout = receiveTimeout;
clientSocket.SendTimeout = sendTimeout;
try
{
clientSocket.Connect(hostname, port);
}
catch (SocketException e)
{
// Close the socket - we are not connected, so no need to close stream underneath
clientSocket.Close();
DefaultLogger.Log.LogError("Connect(): " + e.Message);
throw new PopServerNotFoundException("Server not found", e);
}
Stream stream;
if (useSsl)
{
// If we want to use SSL, open a new SSLStream on top of the open TCP stream.
// We also want to close the TCP stream when the SSL stream is closed
// If a validator was passed to us, use it.
SslStream sslStream;
if (certificateValidator == null)
{
sslStream = new SslStream(clientSocket.GetStream(), false);
}
else
{
sslStream = new SslStream(clientSocket.GetStream(), false, certificateValidator);
}
sslStream.ReadTimeout = receiveTimeout;
sslStream.WriteTimeout = sendTimeout;
// Authenticate the server
sslStream.AuthenticateAsClient(hostname);
stream = sslStream;
}
else
{
// If we do not want to use SSL, use plain TCP
stream = clientSocket.GetStream();
}
// Now do the connect with the same stream being used to read and write to
Connect(stream);
}
/// <summary>
/// 判断连接是否已经存在,已经存在则断开连接
/// Disconnects from POP3 server.
/// Sends the QUIT command before closing the connection, which deletes all the messages that was marked as such.
/// </summary>
public void Disconnect()
{
AssertDisposed();
if (State == ConnectionState.Disconnected)
throw new InvalidUseException("You cannot disconnect a connection which is already disconnected");
try
{
SendCommand("QUIT");
}
finally
{
DisconnectStreams();
}
}
#endregion
#region Authentication methods
/// <summary>
/// Authenticates a user towards the POP server using <see cref="AuthenticationMethod.Auto"/>.<br/>
/// If this authentication fails but you are sure that the username and password is correct, it might
/// be that that the POP3 server is wrongly telling the client it supports <see cref="AuthenticationMethod.Apop"/>.
/// You should try using <see cref="Authenticate(string, string, AuthenticationMethod)"/> while passing <see cref="AuthenticationMethod.UsernameAndPassword"/> to the method.
/// </summary>
/// <param name="username">The username</param>
/// <param name="password">The user password</param>
/// <exception cref="InvalidLoginException">If the user credentials was not accepted</exception>
/// <exception cref="PopServerLockedException">If the server said the the mailbox was locked</exception>
/// <exception cref="ArgumentNullException">If <paramref name="username"/> or <paramref name="password"/> is <see langword="null"/></exception>
/// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
public void Authenticate(string username, string password)
{
AssertDisposed();
Authenticate(username, password, AuthenticationMethod.Auto);
}
/// <summary>
/// Authenticates a user towards the POP server using some <see cref="AuthenticationMethod"/>.
/// </summary>
/// <param name="username">The username</param>
/// <param name="password">The user password</param>
/// <param name="authenticationMethod">The way that the client should authenticate towards the server</param>
/// <exception cref="NotSupportedException">If <see cref="AuthenticationMethod.Apop"/> is used, but not supported by the server</exception>
/// <exception cref="InvalidLoginException">If the user credentials was not accepted</exception>
/// <exception cref="PopServerLockedException">If the server said the the mailbox was locked</exception>
/// <exception cref="ArgumentNullException">If <paramref name="username"/> or <paramref name="password"/> is <see langword="null"/></exception>
/// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
public void Authenticate(string username, string password, AuthenticationMethod authenticationMethod)
{
AssertDisposed();
if (username == null)
throw new ArgumentNullException("username");
if (password == null)
throw new ArgumentNullException("password");
if (State != ConnectionState.Authorization)
throw new InvalidUseException("You have to be connected and not authorized when trying to authorize yourself");
try
{
switch (authenticationMethod)
{
case AuthenticationMethod.UsernameAndPassword:
AuthenticateUsingUserAndPassword(username, password);
break;
case AuthenticationMethod.Apop:
AuthenticateUsingApop(username, password);
break;
case AuthenticationMethod.Auto:
if (ApopSupported)
AuthenticateUsingApop(username, password);
else
AuthenticateUsingUserAndPassword(username, password);
break;
case AuthenticationMethod.CramMd5:
AuthenticateUsingCramMd5(username, password);
break;
}
}
catch (PopServerException e)
{
DefaultLogger.Log.LogError("Problem logging in using method " + authenticationMethod + ". Server response was: " + LastServerResponse);
// Throw a more specific exception if special cases of failure is detected
// using the response the server generated when the last command was sent
CheckFailedLoginServerResponse(LastServerResponse, e);
// If no special failure is detected, tell that the login credentials were wrong
throw new InvalidLoginException(e);
}
// We are now authenticated and therefore we enter the transaction state
State = ConnectionState.Transaction;
}
/// <summary>
/// Authenticates a user towards the POP server using the USER and PASSWORD commands
/// </summary>
/// <param name="username">The username</param>
/// <param name="password">The user password</param>
/// <exception cref="PopServerException">If the server responded with -ERR</exception>
private void AuthenticateUsingUserAndPassword(string username, string password)
{
SendCommand("USER " + username);
SendCommand("PASS " + password);
// Authentication was successful if no exceptions thrown before getting here
}
/// <summary>
/// Authenticates a user towards the POP server using APOP
/// </summary>
/// <param name="username">The username</param>
/// <param name="password">The user password</param>
/// <exception cref="NotSupportedException">Thrown when the server does not support APOP</exception>
/// <exception cref="PopServerException">If the server responded with -ERR</exception>
private void AuthenticateUsingApop(string username, string password)
{
if (!ApopSupported)
throw new NotSupportedException("APOP is not supported on this server");
SendCommand("APOP " + username + " " + Apop.ComputeDigest(password, ApopTimeStamp));
// Authentication was successful if no exceptions thrown before getting here
}
/// <summary>
/// Authenticates using the CRAM-MD5 authentication method
/// </summary>
/// <param name="username">The username</param>
/// <param name="password">The user password</param>
/// <exception cref="NotSupportedException">Thrown when the server does not support AUTH CRAM-MD5</exception>
/// <exception cref="InvalidLoginException">If the user credentials was not accepted</exception>
/// <exception cref="PopServerLockedException">If the server said the the mailbox was locked</exception>
/// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
private void AuthenticateUsingCramMd5(string username, string password)
{
// Example of communication:
// C: AUTH CRAM-MD5
// S: + PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+
// C: dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw
// S: +OK CRAM authentication successful
// Other example, where AUTH CRAM-MD5 is not supported
// C: AUTH CRAM-MD5
// S: -ERR Authentication method CRAM-MD5 not supported
try
{
SendCommand("AUTH CRAM-MD5");
}
catch (PopServerException e)
{
// A PopServerException will be thrown if the server responds with a -ERR not supported
throw new NotSupportedException("CRAM-MD5 authentication not supported", e);
}
// Fetch out the challenge from the server response
string challenge = LastServerResponse.Substring(2);
// Compute the challenge response
string response = CramMd5.ComputeDigest(username, password, challenge);
// Send the response to the server
SendCommand(response);
// Authentication was successful if no exceptions thrown before getting here
}
#endregion
#region Public POP3 commands
/// <summary>
/// Get the number of messages on the server using a STAT command
/// </summary>
/// <returns>The message count on the server</returns>
/// <exception cref="PopServerException">If the server did not accept the STAT command</exception>
public int GetMessageCount()
{
AssertDisposed();
if (State != ConnectionState.Transaction)
throw new InvalidUseException("You cannot get the message count without authenticating yourself towards the server first");
return SendCommandIntResponse("STAT", 1);
}
/// <summary>
/// Marks the message with the given message number as deleted.<br/>
/// <br/>
/// The message will not be deleted until a QUIT command is sent to the server.<br/>
/// This is done when you call <see cref="Disconnect()"/> or when the Pop3Client is <see cref="Dispose">Disposed</see>.
/// </summary>
/// <param name="messageNumber">
/// The number of the message to be deleted. This message may not already have been deleted.<br/>
/// The <paramref name="messageNumber"/> must be inside the range [1, messageCount]
/// </param>
/// <exception cref="PopServerException">If the server did not accept the delete command</exception>
public void DeleteMessage(int messageNumber)
{
AssertDisposed();
ValidateMessageNumber(messageNumber);
if (State != ConnectionState.Transaction)
throw new InvalidUseException("You cannot delete any messages without authenticating yourself towards the server first");
SendCommand("DELE " + messageNumber);
}
/// <summary>
/// Marks all messages as deleted.<br/>
/// <br/>
/// The messages will not be deleted until a QUIT command is sent to the server.<br/>
/// This is done when you call <see cref="Disconnect()"/> or when the Pop3Client is <see cref="Dispose">Disposed</see>.<br/>
/// The method assumes that no prior message has been marked as deleted, and is not valid to call if this is wrong.
/// </summary>
/// <exception cref="PopServerException">If the server did not accept one of the delete commands. All prior marked messages will still be marked.</exception>
public void DeleteAllMessages()
{
AssertDisposed();
int messageCount = GetMessageCount();
for (int messageItem = messageCount; messageItem > 0; messageItem--)
{
DeleteMessage(messageItem);
}
}
/// <summary>
/// Keep server active by sending a NOOP command.<br/>
/// This might keep the server from closing the connection due to inactivity.<br/>
/// <br/>
/// RFC:<br/>
/// The POP3 server does nothing, it merely replies with a positive response.
/// </summary>
/// <exception cref="PopServerException">If the server did not accept the NOOP command</exception>
public void NoOperation()
{
AssertDisposed();
if (State != ConnectionState.Transaction)
throw new InvalidUseException("You cannot use the NOOP command unless you are authenticated to the server");
SendCommand("NOOP");
}
/// <summary>
/// Send a reset command to the server.<br/>
/// <br/>
/// RFC:<br/>
/// If any messages have been marked as deleted by the POP3
/// server, they are unmarked. The POP3 server then replies
/// with a positive response.
/// </summary>
/// <exception cref="PopServerException">If the server did not accept the RSET command</exception>
public void Reset()
{
AssertDisposed();
if (State != ConnectionState.Transaction)
throw new InvalidUseException("You cannot use the RSET command unless you are authenticated to the server");
SendCommand("RSET");
}
/// <summary>
/// Get a unique ID for a single message.<br/>
/// </summary>
/// <param name="messageNumber">
/// Message number, which may not be marked as deleted.<br/>
/// The <paramref name="messageNumber"/> must be inside the range [1, messageCount]
/// </param>
/// <returns>The unique ID for the message</returns>
/// <exception cref="PopServerException">If the server did not accept the UIDL command. This could happen if the <paramref name="messageNumber"/> does not exist</exception>
public string GetMessageUid(int messageNumber)
{
AssertDisposed();
ValidateMessageNumber(messageNumber);
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot get message ID, when the user has not been authenticated yet");
// Example from RFC:
//C: UIDL 2
//S: +OK 2 QhdPYR:00WBw1Ph7x7
SendCommand("UIDL " + messageNumber);
// Parse out the unique ID
return LastServerResponse.Split(' ')[2];
}
/// <summary>
/// Gets a list of unique IDs for all messages.<br/>
/// Messages marked as deleted are not listed.
/// </summary>
/// <returns>
/// A list containing the unique IDs in sorted order from message number 1 and upwards.
/// </returns>
/// <exception cref="PopServerException">If the server did not accept the UIDL command</exception>
public List<string> GetMessageUids()
{
AssertDisposed();
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot get message IDs, when the user has not been authenticated yet");
// RFC Example:
// C: UIDL
// S: +OK
// S: 1 whqtswO00WBw418f9t5JxYwZ
// S: 2 QhdPYR:00WBw1Ph7x7
// S: . // this is the end
SendCommand("UIDL");
List<string> uids = new List<string>();
string response;
// Keep reading until multi-line ends with a "."
while (!IsLastLineInMultiLineResponse(response = StreamUtility.ReadLineAsAscii(Stream)))
{
// Add the unique ID to the list
uids.Add(response.Split(' ')[1]);
}
return uids;
}
/// <summary>
/// Gets the size in bytes of a single message
/// </summary>
/// <param name="messageNumber">
/// The number of a message which may not be a message marked as deleted.<br/>
/// The <paramref name="messageNumber"/> must be inside the range [1, messageCount]
/// </param>
/// <returns>Size of the message</returns>
/// <exception cref="PopServerException">If the server did not accept the LIST command</exception>
public int GetMessageSize(int messageNumber)
{
AssertDisposed();
ValidateMessageNumber(messageNumber);
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot get message size, when the user has not been authenticated yet");
// RFC Example:
// C: LIST 2
// S: +OK 2 200
return SendCommandIntResponse("LIST " + messageNumber, 2);
}
/// <summary>
/// Get the sizes in bytes of all the messages.<br/>
/// Messages marked as deleted are not listed.
/// </summary>
/// <returns>Size of each message excluding deleted ones</returns>
/// <exception cref="PopServerException">If the server did not accept the LIST command</exception>
public List<int> GetMessageSizes()
{
AssertDisposed();
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot get message sizes, when the user has not been authenticated yet");
// RFC Example:
// C: LIST
// S: +OK 2 messages (320 octets)
// S: 1 120
// S: 2 200
// S: . // End of multi-line
SendCommand("LIST");
List<int> sizes = new List<int>();
string response;
// Read until end of multi-line
while (!".".Equals(response = StreamUtility.ReadLineAsAscii(Stream)))
{
sizes.Add(int.Parse(response.Split(' ')[1], CultureInfo.InvariantCulture));
}
return sizes;
}
/// <summary>
/// Fetches a message from the server and parses it
/// </summary>
/// <param name="messageNumber">
/// Message number on server, which may not be marked as deleted.<br/>
/// Must be inside the range [1, messageCount]
/// </param>
/// <returns>The message, containing the email message</returns>
/// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
public Message GetMessage(int messageNumber)
{
AssertDisposed();
ValidateMessageNumber(messageNumber);
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
byte[] messageContent = GetMessageAsBytes(messageNumber);
return new Message(messageContent);
}
/// <summary>
/// Fetches a message in raw form from the server
/// </summary>
/// <param name="messageNumber">
/// Message number on server, which may not be marked as deleted.<br/>
/// Must be inside the range [1, messageCount]
/// </param>
/// <returns>The raw bytes of the message</returns>
/// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
public byte[] GetMessageAsBytes(int messageNumber)
{
AssertDisposed();
ValidateMessageNumber(messageNumber);
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
// Get the full message
return GetMessageAsBytes(messageNumber, false);
}
/// <summary>
/// Get all the headers for a message.<br/>
/// The server will not need to send the body of the message.
/// </summary>
/// <param name="messageNumber">
/// Message number, which may not be marked as deleted.<br/>
/// Must be inside the range [1, messageCount]
/// </param>
/// <returns>MessageHeaders object</returns>
/// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
public MessageHeader GetMessageHeaders(int messageNumber)
{
AssertDisposed();
ValidateMessageNumber(messageNumber);
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
// Only fetch the header part of the message
byte[] messageContent = GetMessageAsBytes(messageNumber, true);
// Do not parse the body - as it is not in the byte array
return new Message(messageContent, false).Headers;
}
/// <summary>
/// Asks the server to return it's capability listing.<br/>
/// This is an optional command, which a server is not enforced to accept.
/// </summary>
/// <returns>
/// The returned Dictionary keys are the capability names.<br/>
/// The Lists pointed to are the capability parameters fitting that certain capability name.
/// See <a href="http://tools.ietf.org/html/rfc2449#section-6">RFC section 6</a> for explanation for some of the capabilities.
/// </returns>
/// <remarks>
/// Capabilities are case-insensitive.<br/>
/// The dictionary uses case-insensitive searching, but the Lists inside
/// does not. Therefore you will have to use something like the code below
/// to search for a capability parameter.<br/>
/// foo is the capability name and bar is the capability parameter.
/// <code>
/// List<string> arguments = capabilities["foo"];
/// bool contains = null != arguments.Find(delegate(string str)
/// {
/// return String.Compare(str, "bar", true) == 0;
/// });
/// </code>
/// If we were running on .NET framework >= 3.5, a HashSet could have been used.
/// </remarks>
/// <exception cref="PopServerException">If the server did not accept the capability command</exception>
public Dictionary<string, List<string>> Capabilities()
{
AssertDisposed();
if (State != ConnectionState.Authorization && State != ConnectionState.Transaction)
throw new InvalidUseException("Capability command only available while connected or authenticated");
// RFC Example
// Examples:
// C: CAPA
// S: +OK Capability list follows
// S: TOP
// S: USER
// S: SASL CRAM-MD5 KERBEROS_V4
// S: RESP-CODES
// S: LOGIN-DELAY 900
// S: PIPELINING
// S: EXPIRE 60
// S: UIDL
// S: IMPLEMENTATION Shlemazle-Plotz-v302
// S: .
SendCommand("CAPA");
// Capablities are case-insensitive
Dictionary<string, List<string>> capabilities = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
string lineRead;
// Keep reading until we are at the end of the multi line response
while (!IsLastLineInMultiLineResponse(lineRead = StreamUtility.ReadLineAsAscii(Stream)))
{
// Example of read line
// SASL CRAM-MD5 KERBEROS_V4
// SASL is the name of the capability while
// CRAM-MD5 and KERBEROS_V4 are arguments to SASL
string[] splitted = lineRead.Split(' ');
// There should always be a capability name
string capabilityName = splitted[0];
// Find all the arguments
List<string> capabilityArguments = new List<string>();
for (int i = 1; i < splitted.Length; i++)
{
capabilityArguments.Add(splitted[i]);
}
// Add the capability found to the dictionary
capabilities.Add(capabilityName, capabilityArguments);
}
return capabilities;
}
#endregion
#region Private helper methods
/// <summary>
/// Examines string to see if it contains a time stamp to use with the APOP command.<br/>
/// If it does, sets the <see cref="ApopTimeStamp"/> property to this value.
/// </summary>
/// <param name="response">The string to examine</param>
/// <exception cref="ArgumentNullException">If <paramref name="response"/> is <see langword="null"/></exception>
private void ExtractApopTimestamp(string response)
{
if (response == null)
throw new ArgumentNullException("response");
// RFC Example:
// +OK POP3 server ready <[email protected]>
Match match = Regex.Match(response, "<.+>");
if (match.Success)
{
ApopTimeStamp = match.Value;
ApopSupported = true;
}
}
/// <summary>
/// Tests a string to see if it is a "+" string.<br/>
/// An "+" string should be returned by a compliant POP3
/// server if the request could be served.<br/>
/// <br/>
/// The method does only check if it starts with "+".
/// </summary>
/// <param name="response">The string to examine</param>
/// <exception cref="PopServerException">Thrown if server did not respond with "+" message</exception>
private static void IsOkResponse(string response)
{
if (response == null)
throw new PopServerException("The stream used to retrieve responses from was closed");
if (response.StartsWith("+", StringComparison.OrdinalIgnoreCase))
return;
throw new PopServerException("The server did not respond with a + response. The response was: \"" + response + "\"");
}
/// <summary>
/// Sends a command to the POP server.<br/>
/// If this fails, an exception is thrown.
/// </summary>
/// <param name="command">The command to send to server</param>
/// <exception cref="PopServerException">If the server did not send an OK message to the command</exception>
private void SendCommand(string command)
{
// Convert the command with CRLF afterwards as per RFC to a byte array which we can write
byte[] commandBytes = Encoding.ASCII.GetBytes(command + "\r\n");
// Write the command to the server
Stream.Write(commandBytes, 0, commandBytes.Length);
Stream.Flush(); // Flush the content as we now wait for a response
// Read the response from the server. The response should be in ASCII
LastServerResponse = StreamUtility.ReadLineAsAscii(Stream);
IsOkResponse(LastServerResponse);
}
/// <summary>
/// Sends a command to the POP server, expects an integer reply in the response
/// </summary>
/// <param name="command">command to send to server</param>
/// <param name="location">
/// The location of the int to return.<br/>
/// Example:<br/>
/// <c>S: +OK 2 200</c><br/>
/// Set <paramref name="location"/>=1 to get 2<br/>
/// Set <paramref name="location"/>=2 to get 200<br/>
/// </param>
/// <returns>Integer value in the reply</returns>
/// <exception cref="PopServerException">If the server did not accept the command</exception>
private int SendCommandIntResponse(string command, int location)
{
SendCommand(command);
return int.Parse(LastServerResponse.Split(' ')[location], CultureInfo.InvariantCulture);
}
/// <summary>
/// Asks the server for a message and returns the message response as a byte array.
/// </summary>
/// <param name="messageNumber">
/// Message number on server, which may not be marked as deleted.<br/>
/// Must be inside the range [1, messageCount]
/// </param>
/// <param name="askOnlyForHeaders">If <see langword="true"/> only the header part of the message is requested from the server. If <see langword="false"/> the full message is requested</param>
/// <returns>A byte array that the message requested consists of</returns>
/// <exception cref="PopServerException">If the server did not accept the command sent to fetch the message</exception>
private byte[] GetMessageAsBytes(int messageNumber, bool askOnlyForHeaders)
{
AssertDisposed();
ValidateMessageNumber(messageNumber);
if (State != ConnectionState.Transaction)
throw new InvalidUseException("Cannot fetch a message, when the user has not been authenticated yet");
if (askOnlyForHeaders)
{
// 0 is the number of lines of the message body to fetch, therefore it is set to zero to fetch only headers
SendCommand("TOP " + messageNumber + " 0");
}
else
{
// Ask for the full message
SendCommand("RETR " + messageNumber);
}
// RFC 1939 Example
// C: RETR 1
// S: +OK 120 octets
// S: <the POP3 server sends the entire message here>
// S: .
// Create a byte array builder which we use to write the bytes too
// When done, we can get the byte array out
using (MemoryStream byteArrayBuilder = new MemoryStream())
{
bool first = true;
byte[] lineRead;
// Keep reading until we are at the end of the multi line response
while (!IsLastLineInMultiLineResponse(lineRead = StreamUtility.ReadLineAsBytes(Stream)))
{
// We should not write CRLF on the very last line, therefore we do this
if (!first)
{
// Write CRLF which was not included in the lineRead bytes of last line
byte[] crlfPair = Encoding.ASCII.GetBytes("\r\n");
byteArrayBuilder.Write(crlfPair, 0, crlfPair.Length);
}
else
{
// We are now not the first anymore
first = false;
}
// This is a multi-line. See http://tools.ietf.org/html/rfc1939#section-3
// It says that a line starting with "." and not having CRLF after it
// is a multi line, and the "." should be stripped
if (lineRead.Length > 0 && lineRead[0] == '.')
{
// Do not write the first period
byteArrayBuilder.Write(lineRead, 1, lineRead.Length - 1);
}
else
{
// Write everything
byteArrayBuilder.Write(lineRead, 0, lineRead.Length);
}
}
// If we are fetching a header - add an extra line to denote the headers ended
if (askOnlyForHeaders)
{
byte[] crlfPair = Encoding.ASCII.GetBytes("\r\n");
byteArrayBuilder.Write(crlfPair, 0, crlfPair.Length);
}
// Get out the bytes we have written to byteArrayBuilder
byte[] receivedBytes = byteArrayBuilder.ToArray();
return receivedBytes;
}
}
/// <summary>
/// Check if the bytes received is the last line in a multi line response
/// from the pop3 server. It is the last line if the line contains only a "."
/// </summary>
/// <param name="bytesReceived">The last line received from the server, which could be the last response line</param>
/// <returns><see langword="true"/> if last line in a multi line response, <see langword="false"/> otherwise</returns>
/// <exception cref="ArgumentNullException">If <paramref name="bytesReceived"/> is <see langword="null"/></exception>
private static bool IsLastLineInMultiLineResponse(byte[] bytesReceived)
{
if (bytesReceived == null)
throw new ArgumentNullException("bytesReceived");
return bytesReceived.Length == 1 && bytesReceived[0] == '.';
}
/// <see cref="IsLastLineInMultiLineResponse(byte[])"> for documentation</see>
private static bool IsLastLineInMultiLineResponse(string lineReceived)
{
if (lineReceived == null)
throw new ArgumentNullException("lineReceived");
// If the string is indeed the last line, then it is okay to do ASCII encoding
// on it. For performance reasons we check if the length is equal to 1
// so that we do not need to decode a long message string just to see if
// it is the last line
return lineReceived.Length == 1 && IsLastLineInMultiLineResponse(Encoding.ASCII.GetBytes(lineReceived));
}
/// <summary>
/// Method for checking that a <paramref name="messageNumber"/> argument given to some method
/// is indeed valid. If not, <see cref="InvalidUseException"/> will be thrown.
/// </summary>
/// <param name="messageNumber">The message number to validate</param>
private static void ValidateMessageNumber(int messageNumber)
{
if (messageNumber <= 0)
throw new InvalidUseException("The messageNumber argument cannot have a value of zero or less. Valid messageNumber is in the range [1, messageCount]");
}
/// <summary>
/// Closes down the streams and sets the Pop3Client into the initial configuration
/// </summary>
private void DisconnectStreams()
{
try
{
Stream.Close();
}
finally
{
// Reset values to initial state
SetInitialValues();
}
}
/// <summary>
/// Sets the initial values on the public properties of this Pop3Client.
/// </summary>
private void SetInitialValues()
{
// We have not seen the APOPTimestamp yet
ApopTimeStamp = null;
// We are not connected
Connected = false;
State = ConnectionState.Disconnected;
// APOP is not supported before we check on login
ApopSupported = false;
}
/// <summary>
/// Checks for extra response codes when an authentication has failed and throws
/// the correct exception.
/// If no such response codes is found, nothing happens.
/// </summary>
/// <param name="serverErrorResponse">The server response string</param>
/// <param name="e">The exception thrown because the server responded with -ERR</param>
/// <exception cref="PopServerLockedException">If the account is locked or in use</exception>
/// <exception cref="LoginDelayException">If the server rejects the login because of too recent logins</exception>
private static void CheckFailedLoginServerResponse(string serverErrorResponse, PopServerException e)
{
string upper = serverErrorResponse.ToUpperInvariant();
// Bracketed strings are extra response codes addded
// in RFC http://tools.ietf.org/html/rfc2449
// together with the CAPA command.
// Specifies the account is in use
if (upper.Contains("[IN-USE]") || upper.Contains("LOCK"))
{
DefaultLogger.Log.LogError("Authentication: maildrop is locked or in-use");
throw new PopServerLockedException(e);
}
// Specifies that there must go some time between logins
if (upper.Contains("[LOGIN-DELAY]"))
{
throw new LoginDelayException(e);
}
}
#endregion
}
}
7.读取stream数据,数据解码
using System;
using System.IO;
using System.Text;
namespace Sample.MailRelay
{
/// <summary>
/// Utility to help reading bytes and strings of a <see cref="Stream"/>
/// </summary>
internal static class StreamUtility
{
/// <summary>
/// Read a line from the stream.
/// A line is interpreted as all the bytes read until a CRLF or LF is encountered.<br/>
/// CRLF pair or LF is not included in the string.
/// </summary>
/// <param name="stream">The stream from which the line is to be read</param>
/// <returns>A line read from the stream returned as a byte array or <see langword="null"/> if no bytes were readable from the stream</returns>
/// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null"/></exception>
public static byte[] ReadLineAsBytes(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
using (MemoryStream memoryStream = new MemoryStream())
{
while (true)
{
int justRead = stream.ReadByte();
if (justRead == -1 && memoryStream.Length > 0)
break;
// Check if we started at the end of the stream we read from
// and we have not read anything from it yet
if (justRead == -1 && memoryStream.Length == 0)
return null;
char readChar = (char)justRead;
// Do not write \r or \n
if (readChar != '\r' && readChar != '\n')
memoryStream.WriteByte((byte)justRead);
// Last point in CRLF pair
if (readChar == '\n')
break;
}
return memoryStream.ToArray();
}
}
/// <summary>
/// Read a line from the stream. <see cref="ReadLineAsBytes"/> for more documentation.
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <returns>A line read from the stream or <see langword="null"/> if nothing could be read from the stream</returns>
/// <exception cref="ArgumentNullException">If <paramref name="stream"/> is <see langword="null"/></exception>
public static string ReadLineAsAscii(Stream stream)
{
byte[] readFromStream = ReadLineAsBytes(stream);
return readFromStream != null ? Encoding.ASCII.GetString(readFromStream) : null;
}
}
}
8.主函数调用方法
static void mian()
{
WithSSL();
}
public void WithSSL()
{
if (pop3Client.Connected)
pop3Client.Disconnect();
pop3Client.Connect("pop.qq.com", 995, true);
pop3Client.Authenticate("*****@qq.com", "******");
int count = pop3Client.GetMessageCount();
Message message = pop3Client.GetMessage(1);
MessagePart plainTextPart = message.FindFirstPlainTextVersion();
string a1;
if (plainTextPart != null)
{
// The message had a text/plain version - show that one
a1 = plainTextPart.GetBodyAsText();
}
else
{
// Try to find a body to show in some of the other text versions
List<MessagePart> textVersions = message.FindAllTextVersions();
if (textVersions.Count >= 1)
a1 = textVersions[0].GetBodyAsText();
else
a1 = "<<OpenPop>> Cannot find a text version body in this message to show <<OpenPop>>";
}
}